diff --git a/DEPS b/DEPS
index cc22b80..d3713a70 100644
--- a/DEPS
+++ b/DEPS
@@ -312,7 +312,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '9333e227108653e751321a2420f88ae6dc2156ae',
+  'skia_revision': '28172a4e03af8430d1bb6497116baa4b260ad4eb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -320,7 +320,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'a326d25100d1d09038de1d63d1d0676a59b24ffe',
+  'angle_revision': '8807d22fb5c5cb62eb4c8233abfb0d5c6af1e999',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -328,7 +328,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '65f4269f6accf48306adb7fff10c07d72d56b1ea',
+  'pdfium_revision': 'fcfdf1c3e22ed9d0f2341d596297393cc7f5ad53',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -376,7 +376,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '87c9af3b7c6495ab0a0ba86a3eb6a58ff84eca85',
+  'catapult_revision': '0f9739da7f76bbb5bc9c108986880d5cfb9b9cf6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -392,7 +392,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling fuzztest
   # and whatever else without interference from each other.
-  'fuzztest_revision': '362a279f0ad018574278e72c4e98e2b99d3991bb',
+  'fuzztest_revision': 'f67a6fe9ca3afc4d71997e75e8d87c571f0c30cb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling domato
   # and whatever else without interference from each other.
@@ -400,7 +400,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': 'c804bd56a0e5089b60ad36855f181564fa114789',
+  'devtools_frontend_revision': 'e27f62c91b96b3f59a6322ee587f80bfa2d9ed96',
   # 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.
@@ -424,7 +424,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': 'a1b78e4d9d2b8ac509af227aff8e3004d2ace7f1',
+  'dawn_revision': '0e9fde4e362e8e402c7ca3cc3d241775eb80bdb8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1209,7 +1209,7 @@
       'packages': [
           {
               'package': 'chromium/chrome/android/orderfiles/arm64',
-              'version': 'n9uxQVPbVcuNo7WKh6cF8GTEwiNOlYW3zLSw8rgKd_QC',
+              'version': 'QGPr-BhnUCDY5mxLT01_JynDLbtCIlHQnwYzf-BRbVkC',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -1220,7 +1220,7 @@
       'packages': [
           {
               'package': 'chromium/android_webview/tools/orderfiles/arm',
-              'version': '2KZam7lqGCqeftaNyhjwDqNjREkejFscftF1ea37wU8C',
+              'version': 'p7wwUEovnHoqzaX0O8qupMY55URGa3eG-FptDRcL1EcC',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -1231,7 +1231,7 @@
       'packages': [
           {
               'package': 'chromium/android_webview/tools/orderfiles/arm64',
-              'version': 'nqPFthO4wBvep4dy8M9cXT9h8T-W3Gopeu9XjNrwbDUC',
+              'version': 'GKpAn0wNEFST1SoKqXhbR-c_gBJgaNDs6_BKbqFixh4C',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -1616,7 +1616,7 @@
     'packages': [
       {
         'package': 'chromium/chrome/test/data/variations/cipd',
-        'version': 'PyUZ0c5MMik-Oj9I33AyoTvyVFi-foaJNWZTSzvAxm8C',
+        'version': 'zfX9ru6NDRRxsBzgWZgQLzi8j93t4iEuvOnKcUMuGwgC',
       },
     ],
     'condition': 'non_git_source',
@@ -1728,7 +1728,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'yjDOkdvC492M3KA9EiBGFBmpTkSe0XvVXtHqQqB1gZYC',
+          'version': '_wBJ2hBwWyOSj4dkL0Ttoxv3fb4wX0ZrTBMEDITnWXwC',
       },
     ],
     'condition': 'checkout_android and non_git_source',
@@ -2076,7 +2076,7 @@
     Var('chromium_git') + '/external/github.com/jk-jeon/dragonbox.git' + '@' + 'beeeef91cf6fef89a4d4ba5e95d47ca64ccb3a44',
 
   'src/third_party/eigen3/src':
-    Var('chromium_git') + '/external/gitlab.com/libeigen/eigen.git' + '@' + 'f64d1e0acc2c2c33e325b8dd7b2b4673de2b9f14',
+    Var('chromium_git') + '/external/gitlab.com/libeigen/eigen.git' + '@' + '25dba492e30e19dce3d4bd4cd38af2f5c88c387c',
 
   'src/third_party/emoji-metadata/src': {
     'url': Var('chromium_git') + '/external/github.com/googlefonts/emoji-metadata' + '@' + '045f146fca682a836e01cd265171312bfb300e06',
@@ -2591,7 +2591,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '652bdb7719f30b52b08e506645a7322ff1b2cc6f',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + 'd55bd311eb9a970a8e4fb25cbe5ae016d4d3d812',
+    Var('chromium_git') + '/openscreen' + '@' + '89fa62bbbe45c5cc87c6a5e84ff3f2d03a3656c8',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '75c53b6e853dc12c7b3c771edc9c9c841b15faaa',
@@ -2617,7 +2617,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + 'dcb5e98f9f6e9ba61a95b0a3fef56671b0b6ba83',
+    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + 'fa49f16ad958de750da23698f12aebb9f1ae662f',
 
   'src/base/tracing/test/data': {
     'bucket': 'perfetto',
@@ -2958,16 +2958,16 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@92727ef4b277d71d38659e8679bd9f3c59651401',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@dd5ab0120a15c8373e8cfa6278b23918dbe09dbf',
   'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@e966816ab28ab7cb448d5b33270b43c941b343d4',
   '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@f88a2d766840fc825af1fc065977953ba1fa4a91',
-  'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@1c8c845843ca77f70a862fc94d371d36fe703498',
+  'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@4972c69eb50255b314fc0925ca757c4417e6b6c0',
   'src/third_party/vulkan-headers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Headers@ad9ce1235e88dc09287e19171dfac384db8ec32c',
   'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@e0e501b0ba42df7b3af023470ad068c48a3ac4de',
   'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@7f423e2b242c154e6ace85c804c65462a7d41870',
   'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@738ec97a3f659dd6469bff3c4078ef981b0a343f',
-  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@cbfe559d89689bd5e471a39560e137043fb0dc36',
+  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@9076c341487013d4650377e164fd4816779f501d',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'cb0597213b0fcb999caa9ed08c2f88dc45eb7d50',
@@ -3010,7 +3010,7 @@
     Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'),
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '1b08071e7a4747ae7614aa7898d0d393bbe297a0',
+    Var('webrtc_git') + '/src.git' + '@' + '6f90f4ff7a7fb27e8e6785bf40c5ca46b6254162',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -3143,7 +3143,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/boca_receiver_app/app',
-        'version': 'ewkWY5vLV2opHtR3R6VR4lNryszwqOS8Ix9eBjhk37wC',
+        'version': 'atSa_2QV5PquyB5HeJjF2Rk3RBANWrNylmz0RG2wArkC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3154,7 +3154,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/boca_app/app',
-        'version': 'SXqInJgBvQOHSfho4F_yUGidrbytjcpMIl4uiVkTfMMC',
+        'version': '4FLStbpu96N3F6zLCl-tj_9xA0MuSG4FwTz6f-BUCf0C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3236,7 +3236,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/android_deps/autorolled',
-              'version': 'cA8nRMj8uhS3aXj_1a5gxG7mzG-iKRo3ndtBCv5qszEC',
+              'version': '9DXI8FnmXkL2tti6GWrRX5mKZzOJzOVKFbiQLuE4ThYC',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -3721,7 +3721,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        '2e698554fb2f4d1d3e5a8d6cfce58886a6d43654',
+        'a7d5c8b5113456c2a50f4af09f30f50acd27c707',
       'condition': 'checkout_src_internal',
   },
 
@@ -3793,7 +3793,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        'f0f28e17f585b2fc159c8f571456d38592803f0d',
+        'dc307889eca75f759981b901f51303bd1703a003',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/base/threading/platform_thread.h b/base/threading/platform_thread.h
index 676368f..c9cbddd4 100644
--- a/base/threading/platform_thread.h
+++ b/base/threading/platform_thread.h
@@ -12,10 +12,13 @@
 #include <stddef.h>
 
 #include <array>
+#include <cstdint>
 #include <limits>
 #include <optional>
 #include <ostream>
+#include <string>
 #include <type_traits>
+#include <vector>
 
 #include "base/base_export.h"
 #include "base/memory/raw_ptr.h"
diff --git a/cc/trees/draw_properties_unittest.cc b/cc/trees/draw_properties_unittest.cc
index a3548f1..7bbcf25 100644
--- a/cc/trees/draw_properties_unittest.cc
+++ b/cc/trees/draw_properties_unittest.cc
@@ -65,9 +65,10 @@
     if (layer_impl->layer_tree_impl()
             ->property_trees()
             ->scroll_tree_mutable()
-            .SetScrollOffsetDeltaForTesting(layer_impl->element_id(), delta))
+            .SetScrollOffsetDeltaForTesting(layer_impl->element_id(), delta)) {
       layer_impl->layer_tree_impl()->DidUpdateScrollOffset(
           layer_impl->element_id(), /*pushed_from_main_or_pending_tree=*/false);
+    }
   }
 
   static float MaximumAnimationToScreenScale(LayerImpl* layer_impl) {
@@ -101,8 +102,9 @@
   // impl-side pending tree, and updates pending tree draw properties.
   void Commit(float device_scale_factor = 1.0f) {
     UpdateMainDrawProperties(device_scale_factor);
-    if (!host_impl()->pending_tree())
+    if (!host_impl()->pending_tree()) {
       host_impl()->CreatePendingTree();
+    }
     host()->CommitToPendingTree();
     // TODO(crbug.com/40617417) This call should be handled by
     // FakeLayerTreeHost instead of manually pushing the properties from the
@@ -125,8 +127,9 @@
 
   bool UpdateLayerListContains(int id) const {
     for (const auto& layer : update_layer_list_) {
-      if (layer->id() == id)
+      if (layer->id() == id) {
         return true;
+      }
     }
     return false;
   }
@@ -2035,14 +2038,18 @@
                             const gfx::RectF& mapped_rect) {
   gfx::Transform inverse = map_transform.GetCheckedInverse();
   bool clipped = false;
-  if (!clipped)
+  if (!clipped) {
     MathUtil::ProjectPoint(inverse, mapped_rect.top_right(), &clipped);
-  if (!clipped)
+  }
+  if (!clipped) {
     MathUtil::ProjectPoint(inverse, mapped_rect.origin(), &clipped);
-  if (!clipped)
+  }
+  if (!clipped) {
     MathUtil::ProjectPoint(inverse, mapped_rect.bottom_right(), &clipped);
-  if (!clipped)
+  }
+  if (!clipped) {
     MathUtil::ProjectPoint(inverse, mapped_rect.bottom_left(), &clipped);
+  }
   return clipped;
 }
 
@@ -5771,6 +5778,24 @@
       gfx::Vector2dF(-20, -20),
       GetImpl(anchored.get())->ScreenSpaceTransform().To2dTranslation());
 }
+
+TEST_F(DrawPropertiesAnchorPositionScrollTest, Cycle) {
+  CreateRoot();
+  scoped_refptr<Layer> anchored1 = CreateAnchored(root_.get(), {});
+  scoped_refptr<Layer> anchored2 =
+      CreateAnchored(root_.get(), {anchored1->element_id()});
+
+  // Create a cycle: anchored1 -> anchored2 -> anchored1
+  auto& data1 =
+      GetPropertyTrees(anchored1.get())
+          ->transform_tree_mutable()
+          .EnsureAnchorPositionScrollData(anchored1->transform_tree_index());
+  data1.adjustment_container_ids = {anchored2->element_id()};
+
+  // This should not crash (stack overflow).
+  UpdateMainDrawProperties();
+}
+
 class AnimationScaleFactorTrackingLayerImpl : public LayerImpl {
  public:
   static std::unique_ptr<AnimationScaleFactorTrackingLayerImpl> Create(
@@ -8615,10 +8640,9 @@
   gfx::LinearGradient gradient_mask;
 };
 
-class DrawPropertiesWithLayerTreeTest :
-    public DrawPropertiesTestWithLayerTree,
-    public testing::WithParamInterface<MaskFilterTestCase> {
-};
+class DrawPropertiesWithLayerTreeTest
+    : public DrawPropertiesTestWithLayerTree,
+      public testing::WithParamInterface<MaskFilterTestCase> {};
 
 // In layer tree mode, not using impl-side PropertyTreeBuilder.
 TEST_P(DrawPropertiesWithLayerTreeTest, MaskFilterOnRenderSurface) {
@@ -8632,8 +8656,9 @@
 
   const MaskFilterTestCase test_case = GetParam();
   gfx::LinearGradient gradient_mask = test_case.gradient_mask;
-  if (!gradient_mask.IsEmpty())
+  if (!gradient_mask.IsEmpty()) {
     gradient_mask.AddStep(50, 0x50);
+  }
 
   scoped_refptr<Layer> root = Layer::Create();
   host()->SetRootLayer(root);
@@ -8689,21 +8714,26 @@
   UpdateMainDrawProperties();
   CommitAndActivate();
 
-  EXPECT_NE(test_case.rounded_corners.IsEmpty(),
+  EXPECT_NE(
+      test_case.rounded_corners.IsEmpty(),
       GetRenderSurfaceImpl(child_1)->mask_filter_info().HasRoundedCorners());
-  EXPECT_NE(test_case.rounded_corners.IsEmpty(),
+  EXPECT_NE(
+      test_case.rounded_corners.IsEmpty(),
       GetRenderSurfaceImpl(child_2)->mask_filter_info().HasRoundedCorners());
-  EXPECT_NE(test_case.rounded_corners.IsEmpty(),
+  EXPECT_NE(
+      test_case.rounded_corners.IsEmpty(),
       GetRenderSurfaceImpl(child_3)->mask_filter_info().HasRoundedCorners());
 
-  EXPECT_NE(test_case.gradient_mask.IsEmpty(),
+  EXPECT_NE(
+      test_case.gradient_mask.IsEmpty(),
       GetRenderSurfaceImpl(child_1)->mask_filter_info().HasGradientMask());
-  EXPECT_NE(test_case.gradient_mask.IsEmpty(),
+  EXPECT_NE(
+      test_case.gradient_mask.IsEmpty(),
       GetRenderSurfaceImpl(child_2)->mask_filter_info().HasGradientMask());
-  EXPECT_NE(test_case.gradient_mask.IsEmpty(),
+  EXPECT_NE(
+      test_case.gradient_mask.IsEmpty(),
       GetRenderSurfaceImpl(child_3)->mask_filter_info().HasGradientMask());
-  }
-
+}
 
 INSTANTIATE_TEST_SUITE_P(
     DrawPropertiesWithLayerTreeTests,
@@ -8711,7 +8741,8 @@
     testing::ValuesIn<MaskFilterTestCase>({
         {"WithRoundedCorners", gfx::RoundedCornersF(10.f),
          gfx::LinearGradient::GetEmpty()},
-        {"WithGradientMask", gfx::RoundedCornersF(0.f), gfx::LinearGradient(45)},
+        {"WithGradientMask", gfx::RoundedCornersF(0.f),
+         gfx::LinearGradient(45)},
         {"WithRoundedCornersAndGradientMask", gfx::RoundedCornersF(10.f),
          gfx::LinearGradient(45)},
     }),
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index ac28015..6055bc8 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -1142,7 +1142,6 @@
   // Layer id to Layer map.
   std::unordered_map<int, raw_ptr<Layer, CtnExperimental>> layer_id_map_;
 
-  // This is for layer tree mode only.
   std::unordered_map<ElementId, raw_ptr<Layer, CtnExperimental>, ElementIdHash>
       element_layers_map_;
 
diff --git a/cc/trees/property_tree.cc b/cc/trees/property_tree.cc
index b8a3cc8..da53d6c 100644
--- a/cc/trees/property_tree.cc
+++ b/cc/trees/property_tree.cc
@@ -631,7 +631,13 @@
 gfx::Vector2dF TransformTree::AnchorPositionOffset(
     TransformNode* node,
     int max_updated_node_id,
-    UpdateTransformsData* update_data) {
+    UpdateTransformsData* update_data,
+    base::flat_set<int>& visited) {
+  if (visited.contains(node->id)) {
+    return gfx::Vector2dF();
+  }
+  visited.insert(node->id);
+
   const AnchorPositionScrollData* data = GetAnchorPositionScrollData(node->id);
   if (!data)
     return gfx::Vector2dF();
@@ -665,7 +671,7 @@
       // because AnchorPositionOffset() is the opposite of
       // blink::AnchorPositionScrollData::AccmulatedOffset().
       accumulated_offset -= AnchorPositionOffset(
-          container_transform, max_updated_node_id, update_data);
+          container_transform, max_updated_node_id, update_data, visited);
     }
     if (container_transform_id > max_updated_node_id) {
       // The adjustment depends on a later transform node that may contain
@@ -834,7 +840,9 @@
 
   transform.Translate(StickyPositionOffset(node));
   if (node->anchor_position_scroll_data_id >= 0) {
-    transform.Translate(AnchorPositionOffset(node, node->id - 1, update_data));
+    base::flat_set<int> visited;
+    transform.Translate(
+        AnchorPositionOffset(node, node->id - 1, update_data, visited));
     // Make sure the damage rect is tracked.
     node->SetTransformChanged(DamageReason::kUntracked);
   }
diff --git a/cc/trees/property_tree.h b/cc/trees/property_tree.h
index 3c48c276..1ff0494 100644
--- a/cc/trees/property_tree.h
+++ b/cc/trees/property_tree.h
@@ -104,23 +104,23 @@
   }
   T* FindNodeFromElementId(ElementId id) {
     auto iterator = element_id_to_node_index_.find(id);
-    if (iterator == element_id_to_node_index_.end())
+    if (iterator == element_id_to_node_index_.end()) {
       return nullptr;
+    }
     return Node(iterator->second);
   }
   const T* FindNodeFromElementId(ElementId id) const {
     auto iterator = element_id_to_node_index_.find(id);
-    if (iterator == element_id_to_node_index_.end())
+    if (iterator == element_id_to_node_index_.end()) {
       return nullptr;
+    }
     return Node(iterator->second);
   }
 
   void clear();
   size_t size() const { return nodes_.size(); }
 
-  void set_needs_update(bool needs_update) {
-    needs_update_ = needs_update;
-  }
+  void set_needs_update(bool needs_update) { needs_update_ = needs_update; }
   bool needs_update() const { return needs_update_; }
 
   std::vector<T>& nodes() { return nodes_; }
@@ -358,7 +358,8 @@
   gfx::Vector2dF StickyPositionOffset(TransformNode* node);
   gfx::Vector2dF AnchorPositionOffset(TransformNode* node,
                                       int max_updated_node_id,
-                                      UpdateTransformsData* update_data);
+                                      UpdateTransformsData* update_data,
+                                      base::flat_set<int>& visited);
   void UpdateLocalTransform(TransformNode* node,
                             const ViewportPropertyIds* viewport_property_ids,
                             UpdateTransformsData* update_data);
@@ -649,8 +650,9 @@
   // Returns true if the scroll offset is changed.
   bool SetScrollOffset(ElementId id, const gfx::PointF& scroll_offset);
   void SetScrollOffsetClobberActiveValue(ElementId id) {
-    if (auto* synced_offset = GetSyncedScrollOffset(id))
+    if (auto* synced_offset = GetSyncedScrollOffset(id)) {
       synced_offset->set_clobber_active_value();
+    }
   }
 
   bool SetElasticOverscroll(const ScrollNode& scroll_node,
diff --git a/chrome/VERSION b/chrome/VERSION
index 257d705..9a278040 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=147
 MINOR=0
-BUILD=7709
+BUILD=7710
 PATCH=0
diff --git a/chrome/android/java/res/layout/history_item_view.xml b/chrome/android/java/res/layout/history_item_view.xml
index 3df0a64aa..76df6e2 100644
--- a/chrome/android/java/res/layout/history_item_view.xml
+++ b/chrome/android/java/res/layout/history_item_view.xml
@@ -7,6 +7,28 @@
 
 <org.chromium.chrome.browser.history.HistoryItemView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:background="@drawable/list_item_rounded_background_selector"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content" />
\ No newline at end of file
+    android:layout_height="wrap_content">
+
+    <FrameLayout
+        android:id="@+id/spark_container"
+        android:layout_width="@dimen/history_item_spark_size"
+        android:layout_height="@dimen/history_item_spark_size"
+        android:layout_gravity="start|top"
+        android:layout_marginStart="@dimen/history_item_spark_margin_start"
+        android:layout_marginTop="@dimen/history_item_spark_margin_top"
+        android:visibility="gone">
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@drawable/oval_surface_0" />
+        <org.chromium.ui.widget.ChromeImageView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:padding="@dimen/history_item_spark_padding"
+            android:src="@drawable/ic_spark_24dp"
+            app:tint="@macro/default_icon_color_accent1" />
+    </FrameLayout>
+</org.chromium.chrome.browser.history.HistoryItemView>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index e22b13f..f6e08e82 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -293,6 +293,16 @@
     <!-- 18dp padding is added to each side of the remove button to limit the image width to 20dp (56dp - 2 * 18dp). -->
     <dimen name="history_item_remove_button_lateral_padding">18dp</dimen>
     <dimen name="history_item_leading_padding">2dp</dimen>
+    <dimen name="history_item_spark_size">18dp</dimen>
+    <!--
+        Calculated to align the spark center with the favicon bottom-right corner.
+        Favicon right edge = list_item_default_margin(16dp) + list_item_start_icon_width(36dp) = 52dp.
+        Spark margin start = 52dp - (history_item_spark_size(18dp) / 2) = 43dp.
+    -->
+    <dimen name="history_item_spark_margin_start">43dp</dimen>
+    <!-- Calculated using list_item_min_height(64dp) / 2. -->
+    <dimen name="history_item_spark_margin_top">32dp</dimen>
+    <dimen name="history_item_spark_padding">4dp</dimen>
 
     <!-- Context Menu Dimensions -->
     <!-- Prevent the chip from reaching the edges of the screen for long translations. -->
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryItemView.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryItemView.java
index 1ee6ce0..894bcbb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryItemView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryItemView.java
@@ -52,6 +52,7 @@
     private boolean mIsItemRemoved;
     private BooleanSupplier mShowSourceApp;
     private ChipView mChipView;
+    private View mSparkContainer;
 
     public HistoryItemView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -88,6 +89,12 @@
 
         mChipView = findViewById(R.id.chip);
         mChipView.getPrimaryTextView().setEllipsize(TextUtils.TruncateAt.END);
+
+        mSparkContainer = findViewById(R.id.spark_container);
+        // Ensure the spark is drawn on top of the favicon and its background. This is needed
+        // because the content of xml is added first, then the content from code is added next (on
+        // top of the xml children).
+        mSparkContainer.bringToFront();
     }
 
     @Override
@@ -127,6 +134,22 @@
                     AppCompatResources.getColorStateList(
                             getContext(), R.color.default_text_color_list));
         }
+        updateSparkVisibility();
+    }
+
+    @Override
+    protected void updateView(boolean animate) {
+        super.updateView(animate);
+        updateSparkVisibility();
+    }
+
+    private void updateSparkVisibility() {
+        HistoryItem item = getItem();
+        // The spark should be shown only if the item is not selected (checked), blocked, and is an
+        // actor visit.
+        boolean showSpark =
+                item != null && item.isActorVisit() && !item.wasBlockedVisit() && !isChecked();
+        mSparkContainer.setVisibility(showSpark ? View.VISIBLE : View.GONE);
     }
 
     @Initializer
@@ -218,7 +241,13 @@
                 });
     }
 
+    @VisibleForTesting
     View getRemoveButtonForTests() {
         return mRemoveButton;
     }
+
+    @VisibleForTesting
+    View getSparkContainerForTests() {
+        return mSparkContainer;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/BottomSheetManager.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/BottomSheetManager.java
index 1f72db8..07a2164e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/BottomSheetManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/BottomSheetManager.java
@@ -175,11 +175,11 @@
                     @Override
                     public void onBottomControlsHeightChanged(
                             int bottomControlsHeight, int bottomControlsMinHeight) {
-                        mSheetController.setBottomControlsHeight(bottomControlsHeight);
+                        mSheetController.setBottomControlsOffset(bottomControlsHeight);
                     }
                 };
         mBrowserControlsVisibilityManager.addObserver(mBrowserControlsObserver);
-        mSheetController.setBottomControlsHeight(
+        mSheetController.setBottomControlsOffset(
                 controlsVisibilityManager.getBottomControlsHeight());
 
         mOmniboxFocusObserver =
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/history/HistoryUiTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/history/HistoryUiTest.java
index 2ea373e1..b9a711a2 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/history/HistoryUiTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/history/HistoryUiTest.java
@@ -261,6 +261,66 @@
 
     @Test
     @SmallTest
+    public void testSparkVisibility() {
+        // Use a timestamp older than the ones in setUp() to ensure they appear after Item 1 and 2.
+        long timestamp = mItem2.getTimestamp() - 1000;
+
+        // Item with spark (actor visit, not blocked)
+        HistoryItem actorItem =
+                StubbedHistoryProvider.createHistoryItem(
+                        0, timestamp, /* blockedVisit= */ false, /* isActorVisit= */ true);
+
+        // Item without spark (not actor visit)
+        HistoryItem nonActorItem =
+                StubbedHistoryProvider.createHistoryItem(
+                        1, timestamp - 1, /* blockedVisit= */ false, /* isActorVisit= */ false);
+
+        // Item without spark (actor visit, but blocked)
+        HistoryItem blockedActorItem =
+                StubbedHistoryProvider.createHistoryItem(
+                        2, timestamp - 2, /* blockedVisit= */ true, /* isActorVisit= */ true);
+
+        mHistoryProvider.addItem(actorItem);
+        mHistoryProvider.addItem(nonActorItem);
+        mHistoryProvider.addItem(blockedActorItem);
+
+        mAdapter.startLoadingItems();
+        RobolectricUtil.runAllBackgroundAndUi();
+
+        // Recalculate height and layout to ensure all items are visible.
+        mRecyclerView.measure(0, 0);
+        mHeight = mRecyclerView.getMeasuredHeight();
+        layoutRecyclerView();
+
+        // The items are added after the initial ones in setUp().
+        // setUp() adds 2 items. They are at position 2 and 3.
+        // New items are at 4, 5, 6 because they have older timestamps.
+        Assert.assertEquals(7, mAdapter.getItemCount());
+
+        HistoryItemView actorView = (HistoryItemView) getItemView(4);
+        Assert.assertEquals(actorItem, actorView.getItem());
+        Assert.assertEquals(View.VISIBLE, actorView.getSparkContainerForTests().getVisibility());
+
+        // Select the actor visit item and check that the spark is hidden.
+        toggleItemSelection(4);
+        Assert.assertEquals(View.GONE, actorView.getSparkContainerForTests().getVisibility());
+
+        // Unselect the actor visit item and check that the spark is shown again.
+        toggleItemSelection(4);
+        Assert.assertEquals(View.VISIBLE, actorView.getSparkContainerForTests().getVisibility());
+
+        HistoryItemView nonActorView = (HistoryItemView) getItemView(5);
+        Assert.assertEquals(nonActorItem, nonActorView.getItem());
+        Assert.assertEquals(View.GONE, nonActorView.getSparkContainerForTests().getVisibility());
+
+        HistoryItemView blockedActorView = (HistoryItemView) getItemView(6);
+        Assert.assertEquals(blockedActorItem, blockedActorView.getItem());
+        Assert.assertEquals(
+                View.GONE, blockedActorView.getSparkContainerForTests().getVisibility());
+    }
+
+    @Test
+    @SmallTest
     public void testRemove_AllItems() throws Exception {
         toggleItemSelection(2);
         toggleItemSelection(3);
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index fa39c7807..a43c3b5a 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -19866,10 +19866,10 @@
 
     <!-- Chrome-specific PDF strings -->
     <if expr="enable_glic and enable_pdf">
-      <message name="IDS_PDF_GLIC_SUMMARIZE" translateable="false" desc="Button label for the button to summarize PDFs.">
+      <message name="IDS_PDF_GLIC_SUMMARIZE" desc="Button label for the button to summarize PDFs.">
         Summarize
       </message>
-      <message name="IDS_PDF_GLIC_SUMMARIZE_TOOLTIP" translateable="false" desc="Tooltip for the button to summarize PDFs with Gemini.">
+      <message name="IDS_PDF_GLIC_SUMMARIZE_TOOLTIP" desc="Tooltip for the button to summarize PDFs with Gemini.">
         Summarize with Gemini
       </message>
     </if>
diff --git a/chrome/app/generated_resources_grd/IDS_PDF_GLIC_SUMMARIZE.png.sha1 b/chrome/app/generated_resources_grd/IDS_PDF_GLIC_SUMMARIZE.png.sha1
new file mode 100644
index 0000000..2028fd5
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PDF_GLIC_SUMMARIZE.png.sha1
@@ -0,0 +1 @@
+287446831535446f69bc0d6c8023091a7114c422
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PDF_GLIC_SUMMARIZE_TOOLTIP.png.sha1 b/chrome/app/generated_resources_grd/IDS_PDF_GLIC_SUMMARIZE_TOOLTIP.png.sha1
new file mode 100644
index 0000000..f084de5
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PDF_GLIC_SUMMARIZE_TOOLTIP.png.sha1
@@ -0,0 +1 @@
+a22102e1ff9904b2321497ec1926b751f9fe7a8b
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 9575497..7a5e5447 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1933,6 +1933,7 @@
     "//components/activity_reporter",
     "//components/activity_reporter:buildflags",
     "//components/browser_apis/tab_strip:mojom",
+    "//components/browser_apis/ui_controllers/toolbar:mojom",
     "//components/content_settings/browser/ui",
     "//components/enterprise/connectors/core",
     "//components/enterprise/connectors/core:cloud_content_scanning",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 2dfa669..492ad5f 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -70,6 +70,7 @@
   "+components/breadcrumbs/core",
   "+components/browser_apis/browser_controls",
   "+components/browser_apis/tab_strip",
+  "+components/browser_apis/ui_controllers",
   "+components/browser_sync",
   "+components/browser_ui/accessibility",
   "+components/browser_ui/desktop_windowing/android",
diff --git a/chrome/browser/ash/system/input_device_settings.cc b/chrome/browser/ash/system/input_device_settings.cc
index 7c95a9c7..011d7f8 100644
--- a/chrome/browser/ash/system/input_device_settings.cc
+++ b/chrome/browser/ash/system/input_device_settings.cc
@@ -466,7 +466,8 @@
 // static
 bool InputDeviceSettings::ForceKeyboardDrivenUINavigation() {
   if (policy::EnrollmentRequisitionManager::IsMeetDevice() ||
-      policy::EnrollmentRequisitionManager::IsSharkRequisition()) {
+      policy::EnrollmentRequisitionManager::IsSharkRequisition() ||
+      policy::EnrollmentRequisitionManager::IsSquidDevice()) {
     return true;
   }
 
diff --git a/chrome/browser/autocomplete/chrome_aim_eligibility_service_browsertest.cc b/chrome/browser/autocomplete/chrome_aim_eligibility_service_browsertest.cc
index 06e854d..b43ecac 100644
--- a/chrome/browser/autocomplete/chrome_aim_eligibility_service_browsertest.cc
+++ b/chrome/browser/autocomplete/chrome_aim_eligibility_service_browsertest.cc
@@ -1626,8 +1626,14 @@
 }
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
+#if BUILDFLAG(IS_CHROMEOS)
+// TODO(crbug.com/488467253): Fix and re-enable this test for CrOS.
+#define MAYBE_RefreshesOnPersistentError DISABLED_RefreshesOnPersistentError
+#else
+#define MAYBE_RefreshesOnPersistentError RefreshesOnPersistentError
+#endif
 IN_PROC_BROWSER_TEST_F(ChromeAimEligibilityServiceOAuthBrowserTest,
-                       RefreshesOnPersistentError) {
+                       MAYBE_RefreshesOnPersistentError) {
   base::HistogramTester histogram_tester;
 
   omnibox::AimEligibilityResponse response;
diff --git a/chrome/browser/chrome_browser_interface_binders_webui_parts_desktop.cc b/chrome/browser/chrome_browser_interface_binders_webui_parts_desktop.cc
index 75b255922..6c9d9a8 100644
--- a/chrome/browser/chrome_browser_interface_binders_webui_parts_desktop.cc
+++ b/chrome/browser/chrome_browser_interface_binders_webui_parts_desktop.cc
@@ -93,6 +93,7 @@
 #include "chrome/common/chrome_features.h"
 #include "components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom.h"
 #include "components/browser_apis/browser_controls/browser_controls_api.mojom.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom.h"
 #include "components/commerce/core/mojom/shopping_service.mojom.h"  // nogncheck crbug.com/1125897
 #include "components/contextual_tasks/public/features.h"
 #include "components/data_sharing/public/features.h"
@@ -681,6 +682,7 @@
   if (features::IsWebUIToolbarEnabled()) {
     registry.ForWebUI<WebUIToolbarUI>()
         .Add<browser_controls_api::mojom::BrowserControlsService>()
+        .Add<toolbar_ui_api::mojom::ToolbarUIService>()
         .Add<tracked_element::mojom::TrackedElementHandler>();
   }
 
diff --git a/chrome/browser/content_settings/host_content_settings_map_factory.cc b/chrome/browser/content_settings/host_content_settings_map_factory.cc
index 9bcba12..8bb9e8f 100644
--- a/chrome/browser/content_settings/host_content_settings_map_factory.cc
+++ b/chrome/browser/content_settings/host_content_settings_map_factory.cc
@@ -26,11 +26,11 @@
 #include "extensions/buildflags/buildflags.h"
 #include "ui/webui/webui_allowlist_provider.h"
 
-#if BUILDFLAG(ENABLE_EXTENSIONS)
+#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
 #include "base/trace_event/trace_event.h"
 #include "extensions/browser/api/content_settings/content_settings_custom_extension_provider.h"  // nogncheck
 #include "extensions/browser/api/content_settings/content_settings_service.h"  // nogncheck
-#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
+#endif  // BUILDFLAG(ENABLE_EXTENSIONS_CORE)
 
 #if BUILDFLAG(IS_ANDROID)
 #include "chrome/browser/content_settings/javascript_optimizer_provider_android.h"
@@ -70,7 +70,7 @@
   DependsOn(TemplateURLServiceFactory::GetInstance());
 #endif
   DependsOn(OneTimePermissionsTrackerFactory::GetInstance());
-#if BUILDFLAG(ENABLE_EXTENSIONS)
+#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
   DependsOn(extensions::ContentSettingsService::GetFactoryInstance());
 #endif
 #if BUILDFLAG(IS_CHROMEOS)
@@ -137,7 +137,7 @@
                                  std::move(component_extension_provider));
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
-#if BUILDFLAG(ENABLE_EXTENSIONS)
+#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
   // These must be registered before before the HostSettings are passed over to
   // the IOThread.  Simplest to do this on construction.
   settings_map->RegisterProvider(
@@ -150,7 +150,7 @@
           // the case where profile->IsOffTheRecord() is true? And what is the
           // interaction with profile->IsGuestSession()?
           false));
-#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+#endif  // BUILDFLAG(ENABLE_EXTENSIONS_CORE)
 
   supervised_user::FamilyLinkSettingsService* family_link_settings_service =
       supervised_user::FamilyLinkSettingsServiceFactory::GetForKey(
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 3e62df2d..c81428c 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -595,6 +595,7 @@
     "//extensions/browser",
     "//extensions/browser:crx_installer",
     "//extensions/browser:keepalive",
+    "//extensions/browser/api/content_settings",
     "//extensions/browser/api/storage:settings_namespace",
     "//extensions/browser/api/storage:settings_observer",
     "//extensions/browser/api/system_display:display_info_provider",
@@ -1058,7 +1059,6 @@
       "//device/fido",
       "//extensions:extensions_resources",
       "//extensions/browser/api:extensions_api_client",
-      "//extensions/browser/api/content_settings",
       "//extensions/common:mojom",
       "//extensions/common/api",
       "//extensions/strings",
diff --git a/chrome/browser/extensions/api/BUILD.gn b/chrome/browser/extensions/api/BUILD.gn
index 4331010..02e7063 100644
--- a/chrome/browser/extensions/api/BUILD.gn
+++ b/chrome/browser/extensions/api/BUILD.gn
@@ -31,6 +31,7 @@
     "//chrome/browser/extensions/api/activity_log_private",
     "//chrome/browser/extensions/api/browsing_data",
     "//chrome/browser/extensions/api/commands",
+    "//chrome/browser/extensions/api/content_settings",
     "//chrome/browser/extensions/api/context_menus",
     "//chrome/browser/extensions/api/cookies",
     "//chrome/browser/extensions/api/debugger",
@@ -72,7 +73,6 @@
       "//chrome/browser/extensions/api/autofill_private",
       "//chrome/browser/extensions/api/braille_display_private",
       "//chrome/browser/extensions/api/command_line_private",
-      "//chrome/browser/extensions/api/content_settings",
       "//chrome/browser/extensions/api/experimental_actor",
       "//chrome/browser/extensions/api/experimental_ai_data",
       "//chrome/browser/extensions/api/idltest",
diff --git a/chrome/browser/extensions/api/content_settings/BUILD.gn b/chrome/browser/extensions/api/content_settings/BUILD.gn
index 9d44256..18994d6d 100644
--- a/chrome/browser/extensions/api/content_settings/BUILD.gn
+++ b/chrome/browser/extensions/api/content_settings/BUILD.gn
@@ -4,8 +4,8 @@
 
 import("//extensions/buildflags/buildflags.gni")
 
-assert(enable_extensions,
-       "Cannot depend on extensions because enable_extensions=false.")
+assert(enable_extensions_core,
+       "Cannot depend on extensions because enable_extensions_core=false.")
 
 source_set("content_settings") {
   sources = [
diff --git a/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc b/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
index 4f084909..a8ad2a3 100644
--- a/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
+++ b/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
@@ -23,15 +23,12 @@
 #include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
 #include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_switches.h"
 #include "components/content_settings/core/browser/content_settings_uma_util.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings.h"
-#include "components/keep_alive_registry/keep_alive_types.h"
-#include "components/keep_alive_registry/scoped_keep_alive.h"
 #include "components/permissions/permission_manager.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/common/content_switches.h"
@@ -48,6 +45,11 @@
 #include "net/base/schemeful_site.h"
 #include "net/dns/mock_host_resolver.h"
 
+#if !BUILDFLAG(IS_ANDROID)
+#include "components/keep_alive_registry/keep_alive_types.h"
+#include "components/keep_alive_registry/scoped_keep_alive.h"
+#endif
+
 #if BUILDFLAG(ENABLE_PLUGINS)
 #include "content/public/browser/plugin_service.h"
 #endif
@@ -73,24 +75,28 @@
     // The browser might get closed later (and therefore be destroyed), so we
     // save the profile.
     profile_ = profile();
-
+#if !BUILDFLAG(IS_ANDROID)
     // Closing the last browser window also releases a KeepAlive. Make
     // sure it's not the last one, so the message loop doesn't quit
     // unexpectedly.
     keep_alive_ = std::make_unique<ScopedKeepAlive>(
         KeepAliveOrigin::BROWSER, KeepAliveRestartOption::DISABLED);
+#endif
+
     profile_keep_alive_ = std::make_unique<ScopedProfileKeepAlive>(
         profile_, ProfileKeepAliveOrigin::kBrowserWindow);
   }
 
   void TearDownOnMainThread() override {
     profile_keep_alive_.reset();
+#if !BUILDFLAG(IS_ANDROID)
     // BrowserProcess::Shutdown() needs to be called in a message loop, so we
     // post a task to release the keep alive, then run the message loop.
     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE, base::BindOnce(&std::unique_ptr<ScopedKeepAlive>::reset,
                                   base::Unretained(&keep_alive_), nullptr));
     content::RunAllPendingInMessageLoop();
+#endif
 
     ExtensionApiTest::TearDownOnMainThread();
   }
@@ -273,7 +279,11 @@
 
  private:
   raw_ptr<Profile, AcrossTasksDanglingUntriaged> profile_ = nullptr;
+
+#if !BUILDFLAG(IS_ANDROID)
+  // KeepAlive is not supported nor required on Android.
   std::unique_ptr<ScopedKeepAlive> keep_alive_;
+#endif
   std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive_;
 };
 
@@ -313,9 +323,14 @@
       const ExtensionContentSettingsApiTestWithContextType&) = delete;
 };
 
+// Android only supports MV3 and later, therefore don't need to test for
+// persistent background context.
+#if !BUILDFLAG(IS_ANDROID)
 INSTANTIATE_TEST_SUITE_P(PersistentBackground,
                          ExtensionContentSettingsApiTestWithContextType,
                          ::testing::Values(ContextType::kPersistentBackground));
+#endif
+
 INSTANTIATE_TEST_SUITE_P(ServiceWorker,
                          ExtensionContentSettingsApiTestWithContextType,
                          ::testing::Values(ContextType::kServiceWorker));
diff --git a/chrome/browser/extensions/api/tabs/tabs_apitest.cc b/chrome/browser/extensions/api/tabs/tabs_apitest.cc
index 10c5882f..28009eb 100644
--- a/chrome/browser/extensions/api/tabs/tabs_apitest.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_apitest.cc
@@ -283,6 +283,68 @@
   ASSERT_TRUE(RunExtensionTest("tabs/reload")) << message_;
 }
 
+// Tests various behaviors of highlighting tabs using chrome.tabs.update(),
+// including that highlighting is additive, tabs can be unhighlighted, and
+// that extensions cannot unhighlight all tabs in a window.
+IN_PROC_BROWSER_TEST_F(ExtensionApiTabTest, UpdateHighlighted) {
+  constexpr char kManifest[] = R"({
+    "name": "Update Highlighted",
+    "version": "1.0",
+    "manifest_version": 3,
+    "background": {"service_worker": "background.js"}
+  })";
+
+  constexpr char kBackgroundJs[] = R"(
+    chrome.test.runTests([
+      async function highlightingTabs() {
+        // Open multiple tabs.
+        const win = await chrome.windows.create(
+                        {url: ['about:blank', 'about:blank', 'about:blank']});
+        const tabs = await chrome.tabs.query({windowId: win.id});
+        chrome.test.assertEq(3, tabs.length);
+        // Set the initial state. Only highlight the first tab.
+        // This is necessary because on desktop android, multiple tabs are
+        // highlighted in the newly-created window.
+        await chrome.tabs.update(tabs[0].id, {highlighted: true});
+        await chrome.tabs.update(tabs[1].id, {highlighted: false});
+        await chrome.tabs.update(tabs[2].id, {highlighted: false});
+
+        let highlightedTabs =
+            await chrome.tabs.query({windowId: win.id, highlighted: true});
+        chrome.test.assertEq(1, highlightedTabs.length);
+
+        chrome.test.assertEq(tabs[0].id, highlightedTabs[0].id);
+
+        // Highlight a different tab. Both tabs should be highlighted.
+        await chrome.tabs.update(tabs[2].id, {highlighted: true});
+        highlightedTabs =
+            await chrome.tabs.query({windowId: win.id, highlighted: true});
+        chrome.test.assertEq(2, highlightedTabs.length);
+        let highlightedIds = highlightedTabs.map(t => t.id);
+        chrome.test.assertEq(highlightedIds.sort(),
+                             [tabs[0].id, tabs[2].id].sort());
+
+        // Unhighlight the first tab. Only tabs[2] should be highlighted now.
+        await chrome.tabs.update(tabs[0].id, {highlighted: false});
+        highlightedTabs =
+            await chrome.tabs.query({windowId: win.id, highlighted: true});
+        chrome.test.assertEq(1, highlightedTabs.length);
+        chrome.test.assertEq(tabs[2].id, highlightedTabs[0].id);
+
+        chrome.test.succeed();
+      }
+    ]);
+  )";
+
+  extensions::TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);
+
+  extensions::ResultCatcher catcher;
+  ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath()));
+  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
 class ExtensionApiCaptureTest : public ExtensionApiTabTest {
  public:
   ExtensionApiCaptureTest() = default;
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 479113e..3d3bf704 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -263,6 +263,7 @@
     &kAvoidDoubleMultiwindowChanges,
     &kBlockIntentsWhileLocked,
     &kBookmarkPaneAndroid,
+    &kBottomSheetAsBrowserControls,
     &kBrowserControlsDebugging,
     &kBrowserControlsEarlyResize,
     &kBrowserControlsPersistsOnCvh,
@@ -592,6 +593,7 @@
 BASE_FEATURE(kAvoidDoubleMultiwindowChanges, base::FEATURE_DISABLED_BY_DEFAULT);
 BASE_FEATURE(kBlockIntentsWhileLocked, base::FEATURE_DISABLED_BY_DEFAULT);
 BASE_FEATURE(kBookmarkPaneAndroid, base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kBottomSheetAsBrowserControls, base::FEATURE_ENABLED_BY_DEFAULT);
 BASE_FEATURE(kBrowserControlsDebugging, base::FEATURE_DISABLED_BY_DEFAULT);
 BASE_FEATURE(kBrowserControlsEarlyResize, base::FEATURE_DISABLED_BY_DEFAULT);
 BASE_FEATURE(kBrowserControlsPersistsOnCvh, base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 2f14ec0c..97eb9578 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -81,6 +81,7 @@
 BASE_DECLARE_FEATURE(kBackgroundThreadPool);
 BASE_DECLARE_FEATURE(kBlockIntentsWhileLocked);
 BASE_DECLARE_FEATURE(kBookmarkPaneAndroid);
+BASE_DECLARE_FEATURE(kBottomSheetAsBrowserControls);
 BASE_DECLARE_FEATURE(kBrowserControlsDebugging);
 BASE_DECLARE_FEATURE(kBrowserControlsEarlyResize);
 BASE_DECLARE_FEATURE(kBrowserControlsPersistsOnCvh);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 58f5147..12e182b3 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -298,6 +298,7 @@
     public static final String BACK_FORWARD_CACHE = "BackForwardCache";
     public static final String BLOCK_INTENTS_WHILE_LOCKED = "BlockIntentsWhileLocked";
     public static final String BOOKMARK_PANE_ANDROID = "BookmarkPaneAndroid";
+    public static final String BOTTOM_SHEET_AS_BROWSER_CONTROLS = "BottomSheetAsBrowserControls";
     public static final String BROWSER_CONTROLS_DEBUGGING = "BrowserControlsDebugging";
     public static final String BROWSER_CONTROLS_EARLY_RESIZE = "BrowserControlsEarlyResize";
     public static final String BROWSER_CONTROLS_PERSISTS_ON_CVH = "BrowserControlsPersistsOnCvh";
@@ -799,6 +800,8 @@
             newCachedFlag(BLOCK_INTENTS_WHILE_LOCKED, false);
     public static final CachedFlag sBookmarkPaneAndroid =
             newCachedFlag(BOOKMARK_PANE_ANDROID, false);
+    public static final CachedFlag sBottomSheetAsBrowserControls =
+            newCachedFlag(BOTTOM_SHEET_AS_BROWSER_CONTROLS, true);
     public static final CachedFlag sBrowserControlsDebugging =
             newCachedFlag(BROWSER_CONTROLS_DEBUGGING, false);
     public static final CachedFlag sCacheIsMultiInstanceApi31Enabled =
@@ -1152,6 +1155,7 @@
                     sBackgroundThreadPoolFieldTrial,
                     sBlockIntentsWhileLocked,
                     sBookmarkPaneAndroid,
+                    sBottomSheetAsBrowserControls,
                     sBrowserControlsDebugging,
                     sCacheIsMultiInstanceApi31Enabled,
                     sCctAdaptiveButton,
diff --git a/chrome/browser/resources/contextual_tasks/app.ts b/chrome/browser/resources/contextual_tasks/app.ts
index 96dad18c..be7a3f19 100644
--- a/chrome/browser/resources/contextual_tasks/app.ts
+++ b/chrome/browser/resources/contextual_tasks/app.ts
@@ -67,9 +67,6 @@
     // <if expr="not is_android">
     composebox: ContextualTasksComposeboxElement,
     // </if>
-    // <if expr="is_android">
-    composebox: HTMLElement,
-    // </if>
     composeboxHeaderWrapper: HTMLElement,
     composeboxHeader: HTMLElement,
     flexCenterContainer: HTMLElement,
@@ -563,7 +560,9 @@
       });
     };
 
+    // <if expr="not is_android">
     restartAnimations(this.$.composebox);
+    // </if>
     restartAnimations(this.$.composeboxHeaderWrapper);
 
     if (this.$.nameShimmer) {
diff --git a/chrome/browser/resources/guest_view_shared/slim_web_view.ts b/chrome/browser/resources/guest_view_shared/slim_web_view.ts
index 4344d18..a1883cc3 100644
--- a/chrome/browser/resources/guest_view_shared/slim_web_view.ts
+++ b/chrome/browser/resources/guest_view_shared/slim_web_view.ts
@@ -13,12 +13,6 @@
 import {getHtml} from './slim_web_view.html.js';
 import {BrowserProxyImpl, PermissionResponseAction} from './slim_web_view_browser_proxy.js';
 
-export interface SlimWebViewElement {
-  $: {
-    input: HTMLElement,
-  };
-}
-
 const GUEST_INSTANCE_ID_PENDING: number = 0;
 
 export class ExitEvent extends Event {
diff --git a/chrome/browser/resources/history/side_bar.ts b/chrome/browser/resources/history/side_bar.ts
index 43fbcaa..d94ed24 100644
--- a/chrome/browser/resources/history/side_bar.ts
+++ b/chrome/browser/resources/history/side_bar.ts
@@ -29,9 +29,9 @@
 
 export interface HistorySideBarElement {
   $: {
-    'history': HTMLAnchorElement,
-    'menu': CrMenuSelectorElement,
-    'syncedTabs': HTMLElement,
+    history: HTMLAnchorElement,
+    menu: CrMenuSelectorElement,
+    syncedTabs: HTMLElement,
   };
 }
 
diff --git a/chrome/browser/resources/history/synced_device_manager.css b/chrome/browser/resources/history/synced_device_manager.css
index 8ecbcf4..47b0495 100644
--- a/chrome/browser/resources/history/synced_device_manager.css
+++ b/chrome/browser/resources/history/synced_device_manager.css
@@ -47,11 +47,11 @@
   }
 }
 
-#no-synced-tabs {
+#noSyncedTabs {
   height: 100%;
 }
 
-#sign-in-guide {
+#signInGuide {
   align-items: center;
   display: flex;
   flex-direction: column;
diff --git a/chrome/browser/resources/history/synced_device_manager.html.ts b/chrome/browser/resources/history/synced_device_manager.html.ts
index 2df51aab..e20649d 100644
--- a/chrome/browser/resources/history/synced_device_manager.html.ts
+++ b/chrome/browser/resources/history/synced_device_manager.html.ts
@@ -28,12 +28,12 @@
     </history-synced-device-card>
   `)}
 </div>
-<div id="no-synced-tabs" class="centered-message"
+<div id="noSyncedTabs" class="centered-message"
     ?hidden="${!this.showNoSyncedMessage_()}">
   ${this.noSyncedTabsMessage_()}
 </div>
 
-<div id="sign-in-guide"
+<div id="signInGuide"
     ?hidden="${!this.showSignInGuide_()
       || this.replaceSyncPromosWithSignInPromos_}">
   <div id="sync-promo-illustration"></div>
diff --git a/chrome/browser/resources/history/synced_device_manager.ts b/chrome/browser/resources/history/synced_device_manager.ts
index 3e128b13..0503ed7 100644
--- a/chrome/browser/resources/history/synced_device_manager.ts
+++ b/chrome/browser/resources/history/synced_device_manager.ts
@@ -47,9 +47,9 @@
 
 export interface HistorySyncedDeviceManagerElement {
   $: {
-    'menu': CrLazyRenderLitElement<CrActionMenuElement>,
-    'no-synced-tabs': HTMLElement,
-    'sign-in-guide': HTMLElement,
+    menu: CrLazyRenderLitElement<CrActionMenuElement>,
+    noSyncedTabs: HTMLElement,
+    signInGuide: HTMLElement,
   };
 }
 
diff --git a/chrome/browser/resources/omnibox/BUILD.gn b/chrome/browser/resources/omnibox/BUILD.gn
index 5c2ce13..a9b17c7 100644
--- a/chrome/browser/resources/omnibox/BUILD.gn
+++ b/chrome/browser/resources/omnibox/BUILD.gn
@@ -52,7 +52,7 @@
   ]
 
   ts_tsconfig_base = "tsconfig_base.json"
-  ts_definitions = [ "css.d.ts" ]
+  ts_definitions = [ "//tools/typescript/definitions/pending.d.ts" ]
   ts_deps = [
     "//ui/webui/resources/js:build_ts",
     "//ui/webui/resources/mojo:build_ts",
diff --git a/chrome/browser/resources/omnibox/css.d.ts b/chrome/browser/resources/omnibox/css.d.ts
deleted file mode 100644
index 09bc2d2..0000000
--- a/chrome/browser/resources/omnibox/css.d.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// See https://github.com/microsoft/TypeScript/issues/46135.
-declare module '*.css' {
-  const _default: CSSStyleSheet;
-  export default _default;
-}
diff --git a/chrome/browser/resources/omnibox_popup/aim_app.css b/chrome/browser/resources/omnibox_popup/aim_app.css
index 75ee3d34..6b10f274 100644
--- a/chrome/browser/resources/omnibox_popup/aim_app.css
+++ b/chrome/browser/resources/omnibox_popup/aim_app.css
@@ -99,14 +99,11 @@
   --cr-icon-button-fill-color: var(--cr-composebox-voice-icon-fill-color);
 }
 
-cr-composebox[searchbox-layout-mode='TallBottomContext']::part(voice-icon) {
+cr-composebox[searchbox-layout-mode='TallBottomContext']::part(voice-icon),
+cr-composebox[submit-enabled_][searchbox-layout-mode='TallBottomContext']:not([show-dropdown_])::part(submit) {
   bottom: 6px;
 }
 
-cr-composebox[show-submit_][submit-enabled_][searchbox-layout-mode='TallBottomContext']:not([show-dropdown_])::part(submit) {
-    bottom: 6px;
-}
-
 cr-composebox[searchbox-layout-mode='TallTopContext'] {
   --cr-composebox-submit-button-margin-inline-start: 0;
 }
diff --git a/chrome/browser/resources/omnibox_popup/aim_app.html.ts b/chrome/browser/resources/omnibox_popup/aim_app.html.ts
index be4be1e..f17b6b9 100644
--- a/chrome/browser/resources/omnibox_popup/aim_app.html.ts
+++ b/chrome/browser/resources/omnibox_popup/aim_app.html.ts
@@ -19,6 +19,7 @@
       @close-composebox="${this.onCloseComposebox_}"
       @composebox-submit="${this.onComposeboxSubmit_}"
       .showMenuOnClick="${false}"
+      .shouldShowGhostFiles="${true}"
       entrypoint-name="Omnibox">
   </cr-composebox>
 </div>
diff --git a/chrome/browser/resources/on_device_internals/app.ts b/chrome/browser/resources/on_device_internals/app.ts
index c04d406c..4740a443 100644
--- a/chrome/browser/resources/on_device_internals/app.ts
+++ b/chrome/browser/resources/on_device_internals/app.ts
@@ -17,7 +17,7 @@
 
 export interface OnDeviceInternalsAppElement {
   $: {
-    'tabs': CrTabsElement,
+    tabs: CrTabsElement,
   };
 }
 
diff --git a/chrome/browser/resources/skills/card.ts b/chrome/browser/resources/skills/card.ts
index 341c5c59..e3e55e7 100644
--- a/chrome/browser/resources/skills/card.ts
+++ b/chrome/browser/resources/skills/card.ts
@@ -29,7 +29,6 @@
 
 export interface SkillCardElement {
   $: {
-    cardBody: HTMLElement,
     name: HTMLElement,
     icon: HTMLElement,
     menu: CrActionMenuElement,
diff --git a/chrome/browser/resources/user_education_internals/user_education_internals.html.ts b/chrome/browser/resources/user_education_internals/user_education_internals.html.ts
index 9392498..4701933f 100644
--- a/chrome/browser/resources/user_education_internals/user_education_internals.html.ts
+++ b/chrome/browser/resources/user_education_internals/user_education_internals.html.ts
@@ -136,7 +136,7 @@
       </div>
       <div id="whatsNew">
         <h2>What's New</h2>
-        <if expr="is_chromeos == False">
+        <if expr="not is_chromeos">
           <div class="whats-new-section">
             <h3>Version Override</h3>
             <p>
diff --git a/chrome/browser/resources/user_education_internals/user_education_internals.ts b/chrome/browser/resources/user_education_internals/user_education_internals.ts
index bc25567..fe2b4c864 100644
--- a/chrome/browser/resources/user_education_internals/user_education_internals.ts
+++ b/chrome/browser/resources/user_education_internals/user_education_internals.ts
@@ -16,7 +16,10 @@
 import './user_education_internals_card.js';
 import './user_education_whats_new_internals_card.js';
 
+// <if expr="not is_chromeos">
 import type {CrInputElement} from '//resources/cr_elements/cr_input/cr_input.js';
+// </if>
+
 import {ColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js';
 import {HelpBubbleMixinLit} from 'chrome://resources/cr_components/help_bubble/help_bubble_mixin_lit.js';
 import type {CrMenuSelectorElement} from 'chrome://resources/cr_elements/cr_menu_selector/cr_menu_selector.js';
@@ -35,7 +38,9 @@
     content: HTMLElement,
     errorMessageToast: CrToastElement,
     menu: CrMenuSelectorElement,
+    // <if expr="not is_chromeos">
     whatsNewVersionOverride: CrInputElement,
+    // </if>
   };
 }
 
@@ -385,6 +390,7 @@
     this.ntpPromoPreferencesExpanded_ = e.detail.value;
   }
 
+  // <if expr="not is_chromeos">
   protected onLaunchWhatsNewStagingClick_() {
     this.handler_.launchWhatsNewStaging();
   }
@@ -401,6 +407,7 @@
     this.handler_.updateWhatsNewVersionOverride(versionOverride);
     this.whatsNewVersionToRequest_ = versionOverride;
   }
+  // </if>
 }
 
 declare global {
diff --git a/chrome/browser/resources/webui_toolbar/BUILD.gn b/chrome/browser/resources/webui_toolbar/BUILD.gn
index 1cc48d0..9a8bf99 100644
--- a/chrome/browser/resources/webui_toolbar/BUILD.gn
+++ b/chrome/browser/resources/webui_toolbar/BUILD.gn
@@ -66,10 +66,14 @@
   mojo_files_deps = [
     "//components/browser_apis/browser_controls:mojom_data_model_ts__generator",
     "//components/browser_apis/browser_controls:mojom_ts__generator",
+    "//components/browser_apis/ui_controllers/toolbar:mojom_data_model_ts__generator",
+    "//components/browser_apis/ui_controllers/toolbar:mojom_ts__generator",
   ]
 
   mojo_files = [
     "$root_gen_dir/components/browser_apis/browser_controls/browser_controls_api.mojom-webui.ts",
     "$root_gen_dir/components/browser_apis/browser_controls/browser_controls_api_data_model.mojom-webui.ts",
+    "$root_gen_dir/components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom-webui.ts",
+    "$root_gen_dir/components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom-webui.ts",
   ]
 }
diff --git a/chrome/browser/resources/webui_toolbar/app.ts b/chrome/browser/resources/webui_toolbar/app.ts
index f861c23..c740441 100644
--- a/chrome/browser/resources/webui_toolbar/app.ts
+++ b/chrome/browser/resources/webui_toolbar/app.ts
@@ -13,10 +13,10 @@
 
 import {getCss} from './app.css.js';
 import {getHtml} from './app.html.js';
-import {SplitTabActiveLocation} from './browser_controls_api_data_model.mojom-webui.js';
 import {BrowserProxyImpl, INVALID_NAVIGATION_CONTROLS_STATE_LISTENER_HANDLE} from './browser_proxy.js';
 import type {BrowserProxy, NavigationControlsState, NavigationControlsStateListenerHandle} from './browser_proxy.js';
 import {MetricsRecorder} from './metrics_recorder.js';
+import {SplitTabActiveLocation} from './toolbar_ui_api_data_model.mojom-webui.js';
 
 export class ToolbarAppElement extends CrLitElement {
   static get is() {
@@ -166,7 +166,7 @@
       promises.push(splitTabs.updateComplete);
     }
     Promise.all(promises).then(() => {
-      this.browserProxy_.handler.onPageInitialized();
+      this.browserProxy_.toolbarUIHandler.onPageInitialized();
     });
   }
 }
diff --git a/chrome/browser/resources/webui_toolbar/browser_proxy.ts b/chrome/browser/resources/webui_toolbar/browser_proxy.ts
index d49546f..32794288 100644
--- a/chrome/browser/resources/webui_toolbar/browser_proxy.ts
+++ b/chrome/browser/resources/webui_toolbar/browser_proxy.ts
@@ -4,10 +4,21 @@
 
 import '//resources/js/cr.js';
 
-import {BrowserControlsObserverCallbackRouter, BrowserControlsService} from './browser_controls_api.mojom-webui.js';
+import {BrowserControlsService} from './browser_controls_api.mojom-webui.js';
 import type {BrowserControlsServiceInterface} from './browser_controls_api.mojom-webui.js';
-import {ClickDispositionFlag, ContextMenuType} from './browser_controls_api_data_model.mojom-webui.js';
-import type {NavigationControlsState, ReloadControlState} from './browser_controls_api_data_model.mojom-webui.js';
+import {ClickDispositionFlag} from './browser_controls_api_data_model.mojom-webui.js';
+import {
+  ToolbarUIObserverCallbackRouter,
+  ToolbarUIService,
+} from './toolbar_ui_api.mojom-webui.js';
+import type {ToolbarUIServiceInterface} from './toolbar_ui_api.mojom-webui.js';
+import {
+  ContextMenuType,
+} from './toolbar_ui_api_data_model.mojom-webui.js';
+import type {
+  NavigationControlsState,
+  ReloadControlState,
+} from './toolbar_ui_api_data_model.mojom-webui.js';
 
 export {
   ClickDispositionFlag,
@@ -26,7 +37,8 @@
     NavigationControlsStateListenerHandle = -1;
 
 export interface BrowserProxy {
-  handler: BrowserControlsServiceInterface;
+  browserControlsHandler: BrowserControlsServiceInterface;
+  toolbarUIHandler: ToolbarUIServiceInterface;
 
   /**
    * Records a value in a histogram.
@@ -45,12 +57,14 @@
 }
 
 export class BrowserProxyImpl implements BrowserProxy {
-  private callbackRouter: BrowserControlsObserverCallbackRouter;
-  handler: BrowserControlsServiceInterface;
+  private callbackRouter: ToolbarUIObserverCallbackRouter;
+  browserControlsHandler: BrowserControlsServiceInterface;
+  toolbarUIHandler: ToolbarUIServiceInterface;
 
   private constructor() {
-    this.callbackRouter = new BrowserControlsObserverCallbackRouter();
-    this.handler = BrowserControlsService.getRemote();
+    this.callbackRouter = new ToolbarUIObserverCallbackRouter();
+    this.browserControlsHandler = BrowserControlsService.getRemote();
+    this.toolbarUIHandler = ToolbarUIService.getRemote();
   }
 
   /**
@@ -68,7 +82,7 @@
     const handle =
         this.callbackRouter.onNavigationControlsStateChanged.addListener(
             listener);
-    this.handler.bind().then(fence => {
+    this.toolbarUIHandler.bind().then(fence => {
       listener(fence.state);
       this.callbackRouter.$.bindHandle(fence.updateStream.handle);
     });
diff --git a/chrome/browser/resources/webui_toolbar/reload_button.ts b/chrome/browser/resources/webui_toolbar/reload_button.ts
index 9adb00a..f58c56a 100644
--- a/chrome/browser/resources/webui_toolbar/reload_button.ts
+++ b/chrome/browser/resources/webui_toolbar/reload_button.ts
@@ -143,7 +143,7 @@
       // as true, so that it won't be treated as a normal click.
       this.isLongPressed_ = true;
       if (this.state.isDevtoolsConnected) {
-        BrowserProxyImpl.getInstance().handler.showContextMenu(
+        BrowserProxyImpl.getInstance().toolbarUIHandler.showContextMenu(
             ContextMenuType.kReload, this.contextMenuPosition(),
             MenuSourceType.kLongPress);
       }
@@ -207,11 +207,11 @@
     clearTimeout(this.longPressTimer_);
 
     if (this.state.isNavigationLoading) {
-      BrowserProxyImpl.getInstance().handler.stopLoad();
+      BrowserProxyImpl.getInstance().browserControlsHandler.stopLoad();
     } else {
       // If the shift or ctrl key is pressed, we should reload with cache
       // bypassed.
-      BrowserProxyImpl.getInstance().handler.reloadFromClick(
+      BrowserProxyImpl.getInstance().browserControlsHandler.reloadFromClick(
           /*bypass_cache=*/ e.shiftKey || e.ctrlKey, this.generateFlags(e));
     }
 
@@ -223,7 +223,7 @@
 
   protected onContextmenu_(e: PointerEvent) {
     if (this.state.isDevtoolsConnected) {
-      BrowserProxyImpl.getInstance().handler.showContextMenu(
+      BrowserProxyImpl.getInstance().toolbarUIHandler.showContextMenu(
           ContextMenuType.kReload, this.contextMenuPosition(),
           MenuSourceType.kMouse);
     }
diff --git a/chrome/browser/resources/webui_toolbar/split_tabs_button.ts b/chrome/browser/resources/webui_toolbar/split_tabs_button.ts
index b2f30ba8..ccc6b64 100644
--- a/chrome/browser/resources/webui_toolbar/split_tabs_button.ts
+++ b/chrome/browser/resources/webui_toolbar/split_tabs_button.ts
@@ -10,12 +10,13 @@
 import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
 import type {PropertyValues} from '//resources/lit/v3_0/lit.rollup.js';
 
-import {ContextMenuType, SplitTabActiveLocation} from './browser_controls_api_data_model.mojom-webui.js';
-import type {SplitTabsControlState} from './browser_controls_api_data_model.mojom-webui.js';
+import {SplitTabActiveLocation} from './toolbar_ui_api_data_model.mojom-webui.js';
+import {ContextMenuType} from './toolbar_ui_api_data_model.mojom-webui.js';
 import {type BrowserProxy, BrowserProxyImpl} from './browser_proxy.js';
 import {getCss} from './split_tabs_button.css.js';
 import {getHtml} from './split_tabs_button.html.js';
 import {getClickSourceType, getContextMenuPosition, getContextMenuSourceType} from './toolbar_button.js';
+import type {SplitTabsControlState} from './toolbar_ui_api_data_model.mojom-webui.js';
 
 export class SplitTabsButtonElement extends CrLitElement {
   static get is() {
@@ -88,18 +89,18 @@
   protected onClick(e: Event) {
     if (this.state.isCurrentTabSplit) {
       // If already split, show the action menu.
-      this.browserProxy_.handler.showContextMenu(
+      this.browserProxy_.toolbarUIHandler.showContextMenu(
           ContextMenuType.kSplitTabsAction, this.menuPosition(),
           getClickSourceType(e));
     } else {
       // If not split, enters split view.
-      this.browserProxy_.handler.splitActiveTab();
+      this.browserProxy_.browserControlsHandler.splitActiveTab();
     }
   }
 
   protected onContextmenu(e: MouseEvent) {
     e.preventDefault();
-    this.browserProxy_.handler.showContextMenu(
+    this.browserProxy_.toolbarUIHandler.showContextMenu(
         ContextMenuType.kSplitTabsContext, this.menuPosition(),
         getContextMenuSourceType(e));
   }
diff --git a/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc b/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc
index d63abd5a1..8e51211 100644
--- a/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc
+++ b/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc
@@ -173,40 +173,6 @@
   ~MockSafeBrowsingUIManager() override = default;
 };
 
-// This class waits for page load state that ClientSideDetectionHost observes as
-// a WebContentsObserver. ClientSideDetectionHost observes the two functions
-// listed before starting the preclassification check.
-class PaintObserverWaiter : public content::WebContentsObserver {
- public:
-  explicit PaintObserverWaiter(content::WebContents* web_contents)
-      : WebContentsObserver(web_contents) {}
-
-  void DidFirstVisuallyNonEmptyPaint() override {
-    did_paint_ = true;
-    if (did_fcp_) {
-      run_loop_.Quit();
-    }
-  }
-
-  void OnFirstContentfulPaintInPrimaryMainFrame() override {
-    did_fcp_ = true;
-    if (did_paint_) {
-      run_loop_.Quit();
-    }
-  }
-
-  void Wait() {
-    if (!did_paint_ || !did_fcp_) {
-      run_loop_.Run();
-    }
-  }
-
- private:
-  bool did_paint_ = false;
-  bool did_fcp_ = false;
-  base::RunLoop run_loop_;
-};
-
 std::string set_up_client_side_model() {
   flatbuffers::FlatBufferBuilder builder(1024);
   std::vector<flatbuffers::Offset<flat::Hash>> hashes;
@@ -379,13 +345,7 @@
   fake_csd_service.SendModelToRenderers();
 
   GURL page_url(embedded_test_server()->GetURL("/safe_browsing/malware.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url));
 
   base::RunLoop run_loop;
   fake_csd_service.SetRequestCallback(run_loop.QuitClosure());
@@ -433,13 +393,7 @@
   fake_csd_service.SendModelToRenderers();
 
   GURL page_url(embedded_test_server()->GetURL("/safe_browsing/malware.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url));
 
   base::RunLoop run_loop;
   fake_csd_service.SetRequestCallback(run_loop.QuitClosure());
@@ -503,25 +457,13 @@
   fake_csd_service.SetRequestCallback(run_loop.QuitClosure());
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   // Prerender then activate a phishing page.
   const GURL prerender_url =
       embedded_test_server()->GetURL("/safe_browsing/malware.html");
   prerender_helper().AddPrerender(prerender_url);
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    prerender_helper().NavigatePrimaryPage(prerender_url);
-    paint_waiter.Wait();
-  } else {
-    prerender_helper().NavigatePrimaryPage(prerender_url);
-  }
+  prerender_helper().NavigatePrimaryPage(prerender_url);
 
   // Bypass the pre-classification checks.
   csd_host->OnPhishingPreClassificationDone(
@@ -655,25 +597,13 @@
   fake_csd_service.SetRequestCallback(run_loop.QuitClosure());
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   // Prerender then activate a phishing page.
   const GURL prerender_url =
       embedded_test_server()->GetURL("/safe_browsing/malware.html");
   prerender_helper().AddPrerender(prerender_url);
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    prerender_helper().NavigatePrimaryPage(prerender_url);
-    paint_waiter.Wait();
-  } else {
-    prerender_helper().NavigatePrimaryPage(prerender_url);
-  }
+  prerender_helper().NavigatePrimaryPage(prerender_url);
 
   // Bypass the pre-classification checks.
   csd_host->OnPhishingPreClassificationDone(
@@ -734,25 +664,13 @@
   fake_csd_service.SetRequestCallback(run_loop.QuitClosure());
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   // Prerender then activate a phishing page.
   const GURL prerender_url =
       embedded_test_server()->GetURL("/safe_browsing/malware.html");
   prerender_helper().AddPrerender(prerender_url);
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    prerender_helper().NavigatePrimaryPage(prerender_url);
-    paint_waiter.Wait();
-  } else {
-    prerender_helper().NavigatePrimaryPage(prerender_url);
-  }
+  prerender_helper().NavigatePrimaryPage(prerender_url);
 
   feature_cache_map->Clear();
 
@@ -809,13 +727,7 @@
   fake_csd_service.SendModelToRenderers();
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   // TODO(andysjlim): Navigating to initial page alongside the first page logs
   // the histogram twice. Figure out why.
@@ -861,13 +773,7 @@
   fake_csd_service.SendModelToRenderers();
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   // Navigating to initial page logs the histogram twice.
   histogram_tester.ExpectTotalCount(
@@ -916,22 +822,10 @@
   fake_csd_service.SetRequestCallback(run_loop.QuitClosure());
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   prerender_helper().AddPrerender(initial_url);
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    prerender_helper().NavigatePrimaryPage(initial_url);
-    paint_waiter.Wait();
-  } else {
-    prerender_helper().NavigatePrimaryPage(initial_url);
-  }
+  prerender_helper().NavigatePrimaryPage(initial_url);
 
   EnterActiveTabFullscreen();
   ASSERT_TRUE(RequestKeyboardLock(/*esc_key_locked=*/true));
@@ -998,22 +892,10 @@
   fake_csd_service.SetRequestCallback(run_loop.QuitClosure());
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   prerender_helper().AddPrerender(initial_url);
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    prerender_helper().NavigatePrimaryPage(initial_url);
-    paint_waiter.Wait();
-  } else {
-    prerender_helper().NavigatePrimaryPage(initial_url);
-  }
+  prerender_helper().NavigatePrimaryPage(initial_url);
 
   RequestToLockPointer(true, false);
   ASSERT_TRUE(GetExclusiveAccessManager()
@@ -1132,13 +1014,7 @@
   fake_csd_service.SendModelToRenderers();
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   // TODO(andysjlim): Navigating to initial page alongside the first page logs
   // the histogram twice. Figure out why.
@@ -1200,13 +1076,7 @@
   fake_csd_service.SetRequestCallback(run_loop.QuitClosure());
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   // Bypass the pre-classification check because it would otherwise return
   // "NO_CLASSIFY_PRIVATE_IP".
@@ -1362,13 +1232,7 @@
   fake_csd_service.SendModelToRenderers();
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   histogram_tester.ExpectTotalCount(
       "SBClientPhishing.PreClassificationCheckResult.ClipboardCopyApi", 0);
@@ -1418,13 +1282,7 @@
   fake_csd_service.SetRequestCallback(csd_request_run_loop.QuitClosure());
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   histogram_tester.ExpectTotalCount(
       "SBClientPhishing.PhishingDetectorResult.ClipboardCopyApi", 0);
@@ -1528,13 +1386,7 @@
   base::HistogramTester histogram_tester;
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   histogram_tester.ExpectTotalCount(
       "SBClientPhishing.PreClassificationCheckResult.ClipboardCopyApi", 0);
@@ -1568,13 +1420,7 @@
   base::HistogramTester histogram_tester;
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
 
   histogram_tester.ExpectTotalCount(
       "SBClientPhishing.PreClassificationCheckResult.ClipboardCopyApi", 0);
@@ -1641,13 +1487,7 @@
   GURL NavigateToCreditCardForm() {
     const GURL url(embedded_test_server()->GetURL(
         "/autofill/autofill_creditcard_form.html"));
-    if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-      PaintObserverWaiter paint_waiter(GetWebContents());
-      EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
-      paint_waiter.Wait();
-    } else {
-      EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
-    }
+    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
     return url;
   }
 
@@ -1849,22 +1689,9 @@
   fake_csd_service.SetRequestCallback(run_loop.QuitClosure());
 
   const GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-    paint_waiter.Wait();
-  } else {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
-  }
-
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
   prerender_helper().AddPrerender(initial_url);
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    PaintObserverWaiter paint_waiter(GetWebContents());
-    prerender_helper().NavigatePrimaryPage(initial_url);
-    paint_waiter.Wait();
-  } else {
-    prerender_helper().NavigatePrimaryPage(initial_url);
-  }
+  prerender_helper().NavigatePrimaryPage(initial_url);
 
   csd_host->OnPhishingPreClassificationDone(
       ClientSideDetectionType::FORCE_REQUEST, /*should_classify=*/true,
diff --git a/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc b/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc
index 68f6ba93..67d9757 100644
--- a/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc
+++ b/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc
@@ -79,7 +79,6 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_entry.h"
-#include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test_utils.h"
@@ -126,6 +125,16 @@
 const bool kFalse = false;
 const bool kTrue = true;
 
+std::unique_ptr<content::NavigationSimulator> NavigateAndKeepLoading(
+    content::WebContents* web_contents,
+    const GURL& url) {
+  auto navigation =
+      content::NavigationSimulator::CreateBrowserInitiated(url, web_contents);
+  navigation->SetKeepLoading(true);
+  navigation->Commit();
+  return navigation;
+}
+
 void WaitUntilHighConfidenceAllowlistCheckDone() {
   base::StatisticsRecorder::HistogramWaiter(
       "SBClientPhishing.MatchHighConfidenceAllowlist")
@@ -576,30 +585,11 @@
     }
   }
 
-  void NotifyClientSideDetectionObservers() {
-    content::WebContentsTester::For(web_contents())
-        ->TestDidFirstVisuallyNonEmptyPaint();
-    if (csd_host_) {
-      csd_host_->OnFirstContentfulPaintInPrimaryMainFrame();
-    }
-  }
-
-  void NavigateAndCommit(const GURL& safe_url,
-                         bool reverse_callback_order = false) {
+  void NavigateAndCommit(const GURL& safe_url) {
     controller().LoadURL(safe_url, content::Referrer(),
                          ui::PAGE_TRANSITION_LINK, std::string());
+
     content::WebContentsTester::For(web_contents())->CommitPendingNavigation();
-    if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-      if (!reverse_callback_order) {
-        NotifyClientSideDetectionObservers();
-      } else {
-        if (csd_host_) {
-          csd_host_->OnFirstContentfulPaintInPrimaryMainFrame();
-        }
-        content::WebContentsTester::For(web_contents())
-            ->TestDidFirstVisuallyNonEmptyPaint();
-      }
-    }
   }
 
   void AdvanceTimeTickClock(base::TimeDelta delta) { clock_.Advance(delta); }
@@ -1142,41 +1132,7 @@
   database_manager_->SetAllowlistLookupDetailsForUrl(url, false);
   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
                                 &kFalse);
-  NavigateAndCommit(url);
-  WaitAndCheckPreClassificationChecks();
-
-  fake_phishing_detector_.CheckMessage(&url);
-
-  histogram_tester.ExpectBucketCount(
-      "SBClientPhishing.PreClassificationCheckResult",
-      PreClassificationCheckResult::CLASSIFY, 1);
-  histogram_tester.ExpectBucketCount(
-      "SBClientPhishing.PreClassificationCheckResult.TriggerModel",
-      PreClassificationCheckResult::CLASSIFY, 1);
-  histogram_tester.ExpectBucketCount(
-      "SBClientPhishing.OnDeviceModelSessionAliveOnNewPreclassification", false,
-      1);
-  histogram_tester.ExpectBucketCount(
-      "SBClientPhishing.IntelligentScanOngoingOnNewPreclassification", false,
-      1);
-}
-
-TEST_F(ClientSideDetectionHostTest,
-       TestPreClassificationCheckPassAlternateObserverOrder) {
-  if (base::FeatureList::IsEnabled(kClientSideDetectionKillswitch)) {
-    GTEST_SKIP();
-  }
-
-  base::HistogramTester histogram_tester;
-
-  // Navigate the tab to a page.  We should see a StartPhishingDetection IPC.
-  GURL url("http://host.com/");
-  database_manager_->SetAllowlistLookupDetailsForUrl(url, false);
-  ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
-                                &kFalse);
-
-  NavigateAndCommit(url, /*reverse_callback_order=*/true);
-
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
 
   fake_phishing_detector_.CheckMessage(&url);
@@ -1205,7 +1161,7 @@
   database_manager_->SetAllowlistLookupDetailsForUrl(url, false);
   ExpectPreClassificationChecks(url, &kFalse, &kTrue, nullptr, nullptr,
                                 &kFalse);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
 }
 
@@ -1222,7 +1178,7 @@
   database_manager_->SetAllowlistLookupDetailsForUrl(url, /*match=*/true);
   ExpectPreClassificationChecks(url, &kFalse, &kFalse, nullptr, nullptr,
                                 nullptr);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
 
   histogram_tester.ExpectTotalCount(
@@ -1245,7 +1201,7 @@
   database_manager_->SetAllowlistLookupDetailsForUrl(url, /*match=*/true);
   ExpectPreClassificationChecks(url, &kFalse, &kFalse, nullptr, nullptr,
                                 nullptr);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
 
   histogram_tester.ExpectTotalCount(
@@ -1272,9 +1228,6 @@
   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
                                 &kFalse);
   navigation->Commit();
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    NotifyClientSideDetectionObservers();
-  }
   WaitAndCheckPreClassificationChecks();
 
   fake_phishing_detector_.CheckMessage(&url);
@@ -1290,7 +1243,7 @@
   database_manager_->SetAllowlistLookupDetailsForUrl(url1, false);
   ExpectPreClassificationChecks(url1, &kFalse, &kFalse, &kFalse, &kFalse,
                                 &kFalse);
-  NavigateAndCommit(url1);
+  NavigateAndKeepLoading(web_contents(), url1);
   WaitAndCheckPreClassificationChecks();
 
   fake_phishing_detector_.CheckMessage(&url1);
@@ -1299,7 +1252,7 @@
   database_manager_->SetAllowlistLookupDetailsForUrl(url2, false);
   ExpectPreClassificationChecks(url2, &kFalse, &kFalse, &kFalse, &kFalse,
                                 &kFalse);
-  NavigateAndCommit(url2);
+  NavigateAndKeepLoading(web_contents(), url2);
   WaitAndCheckPreClassificationChecks();
 
   fake_phishing_detector_.CheckMessage(&url2);
@@ -1315,11 +1268,11 @@
   // preclassification check and continue loading, so that url2 can cancel it.
   GURL url1("http://host1.com/");
   database_manager_->SetAllowlistLookupDetailsForUrl(url1, false);
-  NavigateAndCommit(url1);
+  NavigateAndKeepLoading(web_contents(), url1);
 
   GURL url2("http://host2.com/");
   database_manager_->SetAllowlistLookupDetailsForUrl(url2, false);
-  NavigateAndCommit(url2);
+  NavigateAndKeepLoading(web_contents(), url2);
 
   // Navigating to a second page will cancel the preclassification check of the
   // first page.
@@ -1400,9 +1353,6 @@
       content::NavigationSimulator::CreateBrowserInitiated(url, web_contents());
   navigation->Fail(net::ERR_FAILED);
   navigation->CommitErrorPage();
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    NotifyClientSideDetectionObservers();
-  }
   WaitAndCheckPreClassificationChecks();
 
   histogram_tester.ExpectUniqueSample(
@@ -1428,7 +1378,7 @@
   ExpectPreClassificationChecks(url, &kFalse, nullptr, nullptr, nullptr,
                                 &kFalse);
 
-  NavigateAndCommit(url);
+  content::WebContentsTester::For(web_contents())->NavigateAndCommit(url);
   WaitAndCheckPreClassificationChecks();
 
   fake_phishing_detector_.CheckMessage(nullptr);
@@ -1446,7 +1396,7 @@
   database_manager_->SetAllowlistLookupDetailsForUrl(url, false);
   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kTrue,
                                 &kFalse);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
 
   fake_phishing_detector_.CheckMessage(nullptr);
@@ -1461,7 +1411,7 @@
   database_manager_->SetAllowlistLookupDetailsForUrl(url, false);
   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
                                 &kFalse);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
 
   fake_phishing_detector_.CheckMessage(&url);
@@ -1476,7 +1426,7 @@
   GURL url("file://host.com/");
   ExpectPreClassificationChecks(url, &kFalse, nullptr, nullptr, nullptr,
                                 &kFalse);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
 
   fake_phishing_detector_.CheckMessage(nullptr);
@@ -1498,7 +1448,7 @@
   EXPECT_CALL(*ui_manager_.get(), DisplayBlockingPage(_))
       .WillOnce(SaveArg<0>(&resource));
 
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
   EXPECT_EQ(url, resource.url);
   EXPECT_EQ(url, resource.original_url);
@@ -1520,7 +1470,7 @@
   ExpectPreClassificationChecks(url, &kFalse, nullptr, nullptr, nullptr,
                                 &kFalse);
 
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
 
   fake_phishing_detector_.CheckMessage(nullptr);
@@ -2088,7 +2038,7 @@
   database_manager_->SetAllowlistLookupDetailsForUrl(url, /*match=*/true);
   ExpectPreClassificationChecks(url, &kFalse, &kFalse, nullptr, nullptr,
                                 nullptr);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
 
   // Check that the clipboard histograms haven't been recorded yet.
@@ -2132,7 +2082,7 @@
   database_manager_->SetAllowlistLookupDetailsForUrl(url, /*match=*/true);
   ExpectPreClassificationChecks(url, &kFalse, &kFalse, nullptr, nullptr,
                                 nullptr);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
 
   // Check that the clipboard histograms haven't been recorded yet.
@@ -2178,7 +2128,7 @@
   database_manager_->SetAllowlistLookupDetailsForUrl(url, /*match=*/true);
   ExpectPreClassificationChecks(url, &kFalse, &kFalse, nullptr, nullptr,
                                 nullptr);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
 
   // Check that the clipboard histograms haven't been recorded yet.
@@ -2219,7 +2169,7 @@
   database_manager_->SetAllowlistLookupDetailsForUrl(url, /*match=*/true);
   ExpectPreClassificationChecks(url, &kFalse, &kFalse, nullptr, nullptr,
                                 nullptr);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
 
   // Check that the clipboard histograms haven't been recorded yet.
@@ -3813,7 +3763,7 @@
   ExpectPreClassificationChecks(url, &kFalse, nullptr, nullptr, nullptr,
                                 &kFalse);
   EXPECT_CALL(*database_manager_.get(), CheckCsdAllowlistUrl(url, _)).Times(0);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
   fake_phishing_detector_.CheckMessage(&url);
 }
@@ -3829,7 +3779,7 @@
   ExpectPreClassificationChecks(url, &kFalse, nullptr, nullptr, nullptr,
                                 &kFalse);
   EXPECT_CALL(*csd_service_, GetValidCachedResult(url, NotNull())).Times(0);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
   fake_phishing_detector_.CheckMessage(&url);
 }
@@ -3845,7 +3795,7 @@
   ExpectPreClassificationChecks(url, &kFalse, nullptr, nullptr, nullptr,
                                 &kFalse);
   EXPECT_CALL(*csd_service_, AtPhishingReportLimit()).Times(0);
-  NavigateAndCommit(url);
+  NavigateAndKeepLoading(web_contents(), url);
   WaitAndCheckPreClassificationChecks();
   fake_phishing_detector_.CheckMessage(&url);
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/FuseboxSessionState.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/FuseboxSessionState.java
index 897e710d..9b76365 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/FuseboxSessionState.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/FuseboxSessionState.java
@@ -10,6 +10,8 @@
 import org.chromium.base.UserDataHost;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
+import org.chromium.chrome.browser.omnibox.fusebox.ComposeboxQueryControllerBridge;
+import org.chromium.chrome.browser.omnibox.fusebox.FuseboxAttachmentModelList;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.components.omnibox.AutocompleteInput;
@@ -31,6 +33,8 @@
     private AutocompleteInput mAutocompleteInput = new AutocompleteInput();
 
     private @Nullable Profile mProfile;
+    private @Nullable ComposeboxQueryControllerBridge mComposeBoxQueryControllerBridge;
+    private @Nullable FuseboxAttachmentModelList mFuseboxAttachmentModelList;
     private boolean mIsActive;
 
     /**
@@ -77,8 +81,13 @@
      * @param input The initial AutocompleteInput for this session.
      */
     @VisibleForTesting
-    public FuseboxSessionState(AutocompleteInput input) {
+    public FuseboxSessionState(
+            AutocompleteInput input,
+            @Nullable ComposeboxQueryControllerBridge composeboxQueryControllerBridge,
+            @Nullable FuseboxAttachmentModelList fuseboxAttachmentModelList) {
         mAutocompleteInput = input;
+        mComposeBoxQueryControllerBridge = composeboxQueryControllerBridge;
+        mFuseboxAttachmentModelList = fuseboxAttachmentModelList;
     }
 
     /**
@@ -120,7 +129,37 @@
      *     session.
      */
     public void setProfile(@Nullable Profile profile) {
+        if (mProfile == profile) return;
+
+        // Profile has changed. This typically means either
+        // - profile was applied and we want to construct session objects, or
+        // - profile was removed and we want to destroy them.
+        // Technically this also supports profile swap, but that scenario shouldn't ever happen.
         mProfile = profile;
+
+        if (mFuseboxAttachmentModelList != null) {
+            mFuseboxAttachmentModelList.destroy();
+            mFuseboxAttachmentModelList = null;
+        }
+
+        if (mComposeBoxQueryControllerBridge != null) {
+            mComposeBoxQueryControllerBridge.destroy();
+            mComposeBoxQueryControllerBridge = null;
+        }
+
+        // Abort now if we're not creating session controllers.
+        if (mProfile == null || !mIsActive) return;
+
+        mComposeBoxQueryControllerBridge =
+                ComposeboxQueryControllerBridge.createForProfile(mProfile);
+
+        if (mComposeBoxQueryControllerBridge != null) {
+            // Composebox Controller may not be instantiated if locale or policies prohibit AIM.
+            // Create attachments list only if allowed.
+            mFuseboxAttachmentModelList = new FuseboxAttachmentModelList();
+            mFuseboxAttachmentModelList.setComposeboxQueryControllerBridge(
+                    mComposeBoxQueryControllerBridge);
+        }
     }
 
     /**
@@ -141,4 +180,18 @@
     public AutocompleteInput getAutocompleteInput() {
         return mAutocompleteInput;
     }
+
+    /**
+     * @return The current {@link ComposeboxQueryControllerBridge} for this session.
+     */
+    public @Nullable ComposeboxQueryControllerBridge getComposeboxQueryControllerBridge() {
+        return mComposeBoxQueryControllerBridge;
+    }
+
+    /**
+     * @return The current {@link FuseboxAttachmentModelList} for this session.
+     */
+    public @Nullable FuseboxAttachmentModelList getFuseboxAttachmentModelList() {
+        return mFuseboxAttachmentModelList;
+    }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
index 9242548..50fa86e9 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
@@ -1876,6 +1876,13 @@
                 TextView tv = (TextView) view;
                 return tv.getSelectionStart() == tv.getSelectionEnd()
                         && tv.getSelectionEnd() == tv.getText().length();
+            } else if (keyCode == KeyEvent.KEYCODE_DEL) {
+                if (mCurrentInput != null
+                        && !TextUtils.isEmpty(mCurrentInput.getKeyword())
+                        && TextUtils.isEmpty(mUrlCoordinator.getTextWithoutAutocomplete())) {
+                    mCurrentInput.setKeyword(null);
+                    return true;
+                }
             }
             return false;
         }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java
index 8da17a70..ec6d30f 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java
@@ -74,6 +74,7 @@
 import org.chromium.chrome.browser.lens.LensController;
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.multiwindow.MultiInstanceManager;
+import org.chromium.chrome.browser.omnibox.fusebox.ComposeboxQueryControllerBridge;
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxCoordinator;
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxCoordinator.FuseboxState;
 import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader;
@@ -203,6 +204,7 @@
     @Mock private AppBannerManager.Natives mAppBannerManagerJni;
     @Mock private NewTabPageDelegate mNewTabPageDelegate;
     @Mock private FuseboxCoordinator mFuseboxCoordinator;
+    @Mock private ComposeboxQueryControllerBridge mComposeboxBridge;
 
     @Captor private ArgumentCaptor<Runnable> mRunnableCaptor;
     @Captor private ArgumentCaptor<LoadUrlParams> mLoadUrlParamsCaptor;
@@ -273,6 +275,8 @@
         doReturn(mFuseboxStateSupplier).when(mFuseboxCoordinator).getFuseboxStateSupplier();
         doReturn("").when(mUrlCoordinator).getTextWithAutocomplete();
 
+        ComposeboxQueryControllerBridge.setInstanceForTesting(mComposeboxBridge);
+
         AppBannerManagerJni.setInstanceForTesting(mAppBannerManagerJni);
         doReturn(mAppBannerManager)
                 .when(mAppBannerManagerJni)
@@ -765,6 +769,50 @@
     }
 
     @Test
+    public void testOnKey_del_clearsKeyword() {
+        mMediator.onFinishNativeInitialization();
+        mProfileSupplier.set(mProfile);
+        AutocompleteInput input = new AutocompleteInput();
+        input.setKeyword("keyword");
+        mMediator.beginInput(input);
+
+        doReturn("").when(mUrlCoordinator).getTextWithoutAutocomplete();
+        doReturn(KeyEvent.ACTION_DOWN).when(mKeyEvent).getAction();
+
+        assertTrue(mMediator.onKey(mView, KeyEvent.KEYCODE_DEL, mKeyEvent));
+        assertNull(input.getKeyword());
+    }
+
+    @Test
+    public void testOnKey_del_withText() {
+        mMediator.onFinishNativeInitialization();
+        mProfileSupplier.set(mProfile);
+        AutocompleteInput input = new AutocompleteInput();
+        input.setKeyword("keyword");
+        mMediator.beginInput(input);
+
+        doReturn("text").when(mUrlCoordinator).getTextWithoutAutocomplete();
+        doReturn(KeyEvent.ACTION_DOWN).when(mKeyEvent).getAction();
+
+        assertFalse(mMediator.onKey(mView, KeyEvent.KEYCODE_DEL, mKeyEvent));
+        assertEquals("keyword", input.getKeyword());
+    }
+
+    @Test
+    public void testOnKey_del_noKeyword() {
+        mMediator.onFinishNativeInitialization();
+        mProfileSupplier.set(mProfile);
+        AutocompleteInput input = new AutocompleteInput();
+        mMediator.beginInput(input);
+
+        doReturn("").when(mUrlCoordinator).getTextWithoutAutocomplete();
+        doReturn(KeyEvent.ACTION_DOWN).when(mKeyEvent).getAction();
+
+        assertFalse(mMediator.onKey(mView, KeyEvent.KEYCODE_DEL, mKeyEvent));
+        assertNull(input.getKeyword());
+    }
+
+    @Test
     public void testOnKey_escape() {
         doReturn(KeyEvent.ACTION_DOWN).when(mKeyEvent).getAction();
         assertTrue(mMediator.handleEscPress());
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/OmniboxMetrics.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/OmniboxMetrics.java
index 59dc3145..e50e6d8 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/OmniboxMetrics.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/OmniboxMetrics.java
@@ -499,15 +499,6 @@
                 break;
 
             default:
-                // May trigger if nev PageClassifications were added to
-                // third_party/metrics_proto/omnibox_event.proto file,
-                // but have not been reflected here. If that's the case, file a bug for the
-                // author of the new PageClassification.
-                // Last supported value: OTHER_ON_CCT.
-                assert false
-                        : "b/40221519: Invalid page classification: "
-                                + pageClass
-                                + ". Please re-open bug, and attach captured stack trace.";
                 break;
         }
 
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 455a357..a569e079 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
@@ -290,7 +290,8 @@
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         return ((KeyNavigationUtil.isEnter(event)
                                 || KeyNavigationUtil.isGoAnyDirection(event)
-                                || KeyNavigationUtil.isTabNavigation(event))
+                                || KeyNavigationUtil.isTabNavigation(event)
+                                || event.getKeyCode() == KeyEvent.KEYCODE_DEL)
                         && (mKeyDownListener != null
                                 && mKeyDownListener.onKey(this, keyCode, event)))
                 || super_onKeyDown(keyCode, event);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarUnitTest.java
index 5135993a..2daaa69 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarUnitTest.java
@@ -751,7 +751,8 @@
                         KeyEvent.KEYCODE_DPAD_UP,
                         KeyEvent.KEYCODE_DPAD_DOWN,
                         KeyEvent.KEYCODE_DPAD_LEFT,
-                        KeyEvent.KEYCODE_DPAD_RIGHT);
+                        KeyEvent.KEYCODE_DPAD_RIGHT,
+                        KeyEvent.KEYCODE_DEL);
 
         var listener = mock(View.OnKeyListener.class);
         mUrlBar.setKeyDownListener(listener);
@@ -788,7 +789,8 @@
                         KeyEvent.KEYCODE_ENTER,
                         KeyEvent.KEYCODE_NUMPAD_ENTER,
                         KeyEvent.KEYCODE_DPAD_UP,
-                        KeyEvent.KEYCODE_DPAD_DOWN);
+                        KeyEvent.KEYCODE_DPAD_DOWN,
+                        KeyEvent.KEYCODE_DEL);
 
         var listener = mock(View.OnKeyListener.class);
         mUrlBar.setKeyDownListener(listener);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/ComposeboxQueryControllerBridge.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/ComposeboxQueryControllerBridge.java
index 7cfdefd28..f4b1d65 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/ComposeboxQueryControllerBridge.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/ComposeboxQueryControllerBridge.java
@@ -4,11 +4,14 @@
 
 package org.chromium.chrome.browser.omnibox.fusebox;
 
+import androidx.annotation.VisibleForTesting;
+
 import org.jni_zero.CalledByNative;
 import org.jni_zero.JniType;
 import org.jni_zero.NativeMethods;
 
 import org.chromium.base.Callback;
+import org.chromium.base.ResettersForTesting;
 import org.chromium.base.supplier.MonotonicObservableSupplier;
 import org.chromium.base.supplier.ObservableSuppliers;
 import org.chromium.base.supplier.SettableMonotonicObservableSupplier;
@@ -16,12 +19,13 @@
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.components.contextual_search.FileUploadStatus;
+import org.chromium.components.contextual_search.ContextUploadStatus;
 import org.chromium.components.contextual_search.InputState;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.url.GURL;
 
 import java.nio.ByteBuffer;
+import java.util.Optional;
 
 /**
  * Bridge for native Composebox query controller functionality, allowing for management of a
@@ -30,6 +34,9 @@
 @SuppressWarnings("unused")
 @NullMarked
 public class ComposeboxQueryControllerBridge {
+    /** Instance to be used for testing - null value permitted to signify no controller. */
+    @SuppressWarnings("NullableOptional")
+    private static @Nullable Optional<ComposeboxQueryControllerBridge> sInstanceForTesting;
 
     /** Observer for file upload status changes. */
     interface FileUploadObserver {
@@ -37,7 +44,7 @@
          * @param token Unique string identifier for the file.
          * @param status The status of the file's upload.
          */
-        void onFileUploadStatusChanged(String token, @FileUploadStatus int status);
+        void onFileUploadStatusChanged(String token, @ContextUploadStatus int status);
     }
 
     private long mNativeInstance;
@@ -49,6 +56,8 @@
 
     /** Create a new ComposeboxQueryControllerBridge using the given profile. */
     public static @Nullable ComposeboxQueryControllerBridge createForProfile(Profile profile) {
+        if (sInstanceForTesting != null) return sInstanceForTesting.orElse(null);
+
         ComposeboxQueryControllerBridge javaInstance = new ComposeboxQueryControllerBridge();
         long nativeInstance = ComposeboxQueryControllerBridgeJni.get().init(profile, javaInstance);
         if (nativeInstance == 0L) return null;
@@ -119,11 +128,11 @@
                 .addTabContextFromCache(mNativeInstance, tabId);
     }
 
-    void getAimUrl(GURL url, Callback<GURL> callback) {
+    public void getAimUrl(GURL url, Callback<GURL> callback) {
         ComposeboxQueryControllerBridgeJni.get().getAimUrl(mNativeInstance, url, callback);
     }
 
-    void getImageGenerationUrl(GURL url, Callback<GURL> callback) {
+    public void getImageGenerationUrl(GURL url, Callback<GURL> callback) {
         ComposeboxQueryControllerBridgeJni.get()
                 .getImageGenerationUrl(mNativeInstance, url, callback);
     }
@@ -167,8 +176,19 @@
         return mInputStateSupplier;
     }
 
+    @VisibleForTesting
+    public static void setInstanceForTesting(@Nullable ComposeboxQueryControllerBridge instance) {
+        sInstanceForTesting = Optional.ofNullable(instance);
+        ResettersForTesting.register(ComposeboxQueryControllerBridge::resetInstanceForTesting);
+    }
+
+    @VisibleForTesting
+    public static void resetInstanceForTesting() {
+        sInstanceForTesting = null;
+    }
+
     @CalledByNative
-    void onFileUploadStatusChanged(String token, @FileUploadStatus int fileUploadStatus) {
+    void onFileUploadStatusChanged(String token, @ContextUploadStatus int fileUploadStatus) {
         if (mFileUploadObserver != null) {
             mFileUploadObserver.onFileUploadStatusChanged(token, fileUploadStatus);
         }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachmentModelList.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachmentModelList.java
index 1bb31326..0b44901b 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachmentModelList.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachmentModelList.java
@@ -19,7 +19,7 @@
 import org.chromium.chrome.browser.omnibox.fusebox.ComposeboxQueryControllerBridge.FileUploadObserver;
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxAttachmentRecyclerViewAdapter.FuseboxAttachmentType;
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
-import org.chromium.components.contextual_search.FileUploadStatus;
+import org.chromium.components.contextual_search.ContextUploadStatus;
 import org.chromium.components.omnibox.OmniboxFeatures;
 import org.chromium.ui.modelutil.ListObservable.ListObserver;
 import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
@@ -196,7 +196,7 @@
     }
 
     /** Release all resources and mark this instance ready for recycling. */
-    void destroy() {
+    public void destroy() {
         setComposeboxQueryControllerBridge(null);
         mAttachmentUploadFailedListener = null;
     }
@@ -364,15 +364,15 @@
     }
 
     @Override
-    public void onFileUploadStatusChanged(String token, @FileUploadStatus int status) {
+    public void onFileUploadStatusChanged(String token, @ContextUploadStatus int status) {
         if (TextUtils.isEmpty(token)) return;
         FuseboxAttachment pendingAttachment = findAttachmentWithToken(token);
         if (pendingAttachment == null) return;
 
         switch (status) {
-            case FileUploadStatus.VALIDATION_FAILED:
-            case FileUploadStatus.UPLOAD_FAILED:
-            case FileUploadStatus.UPLOAD_EXPIRED:
+            case ContextUploadStatus.VALIDATION_FAILED:
+            case ContextUploadStatus.UPLOAD_FAILED:
+            case ContextUploadStatus.UPLOAD_EXPIRED:
                 if (pendingAttachment.retryUpload(
                         assumeNonNull(mComposeboxQueryControllerBridge))) {
                     break;
@@ -381,7 +381,7 @@
                 pendingAttachment.setUploadIsComplete();
                 remove(pendingAttachment, /* isFailure= */ true);
                 break;
-            case FileUploadStatus.UPLOAD_SUCCESSFUL:
+            case ContextUploadStatus.UPLOAD_SUCCESSFUL:
                 pendingAttachment.setUploadIsComplete();
                 int index = indexOf(pendingAttachment);
                 mModelList.update(index, pendingAttachment);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachmentModelListUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachmentModelListUnitTest.java
index d91a6cf..ffd2422 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachmentModelListUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachmentModelListUnitTest.java
@@ -37,7 +37,7 @@
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxAttachmentRecyclerViewAdapter.FuseboxAttachmentType;
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxMetrics.FuseboxAttachmentButtonType;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.components.contextual_search.FileUploadStatus;
+import org.chromium.components.contextual_search.ContextUploadStatus;
 import org.chromium.components.omnibox.OmniboxFeatures;
 import org.chromium.content_public.browser.RenderWidgetHostView;
 import org.chromium.content_public.browser.WebContents;
@@ -341,7 +341,7 @@
         assertFalse(attachment.isUploadComplete());
 
         mFuseboxAttachmentModelList.onFileUploadStatusChanged(
-                "uploaded-token", FileUploadStatus.UPLOAD_SUCCESSFUL);
+                "uploaded-token", ContextUploadStatus.UPLOAD_SUCCESSFUL);
         assertTrue(attachment.isUploadComplete());
         verifyNoMoreInteractions(mComposeboxQueryControllerBridge);
     }
@@ -392,9 +392,9 @@
         assertFalse(uploadedAttachment.isUploadComplete());
 
         mFuseboxAttachmentModelList.onFileUploadStatusChanged(
-                "pretokenized-token", FileUploadStatus.UPLOAD_SUCCESSFUL);
+                "pretokenized-token", ContextUploadStatus.UPLOAD_SUCCESSFUL);
         mFuseboxAttachmentModelList.onFileUploadStatusChanged(
-                "uploaded-token", FileUploadStatus.UPLOAD_FAILED);
+                "uploaded-token", ContextUploadStatus.UPLOAD_FAILED);
         assertTrue(uploadFailedNotified.get());
 
         assertTrue(preTokenizedAttachment.isUploadComplete());
@@ -521,11 +521,11 @@
 
         when(mComposeboxQueryControllerBridge.addTabContext(tab)).thenReturn("token2");
         mFuseboxAttachmentModelList.onFileUploadStatusChanged(
-                "token", FileUploadStatus.VALIDATION_FAILED);
+                "token", ContextUploadStatus.VALIDATION_FAILED);
         assertEquals("token2", tabAttachment.getToken());
 
         mFuseboxAttachmentModelList.onFileUploadStatusChanged(
-                "token2", FileUploadStatus.VALIDATION_FAILED);
+                "token2", ContextUploadStatus.VALIDATION_FAILED);
         assertTrue(mFuseboxAttachmentModelList.isEmpty());
     }
 
@@ -620,7 +620,7 @@
             FuseboxAttachment attachment = createTestAttachment("test");
             mFuseboxAttachmentModelList.add(attachment);
             mFuseboxAttachmentModelList.onFileUploadStatusChanged(
-                    "token1", FileUploadStatus.UPLOAD_SUCCESSFUL);
+                    "token1", ContextUploadStatus.UPLOAD_SUCCESSFUL);
         }
     }
 
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxCoordinator.java
index f0d67f7..3af33598 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxCoordinator.java
@@ -48,7 +48,6 @@
 import org.chromium.ui.widget.AnchoredPopupWindow.HorizontalOrientation;
 import org.chromium.ui.widget.RectProvider;
 import org.chromium.ui.widget.ViewRectProvider;
-import org.chromium.url.GURL;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -75,7 +74,6 @@
     private final FuseboxAttachmentModelList mModelList;
     private final MonotonicObservableSupplier<TabModelSelector> mTabModelSelectorSupplier;
     private @Nullable FuseboxMediator mMediator;
-    private @Nullable ComposeboxQueryControllerBridge mComposeboxQueryControllerBridge;
     private @Nullable AutocompleteInput mInput;
     private boolean mDefaultSearchEngineIsGoogle = true;
     private TemplateUrlService mTemplateUrlService;
@@ -177,17 +175,6 @@
             mMediator = null;
         }
 
-        if (mComposeboxQueryControllerBridge != null) {
-            mComposeboxQueryControllerBridge.destroy();
-        }
-        mComposeboxQueryControllerBridge =
-                ComposeboxQueryControllerBridge.createForProfile(profile);
-        AutocompleteController.getForProfile(profile)
-                .setComposeboxQueryControllerBridge(mComposeboxQueryControllerBridge);
-        if (mComposeboxQueryControllerBridge == null) return;
-
-        // Set the bridge for the model list to enable tight coupling.
-        mModelList.setComposeboxQueryControllerBridge(mComposeboxQueryControllerBridge);
         mMediator =
                 new FuseboxMediator(
                         mContext,
@@ -195,15 +182,12 @@
                         mWindowAndroid,
                         mModel,
                         assumeNonNull(mViewHolder),
-                        mModelList,
                         mTabModelSelectorSupplier,
-                        mComposeboxQueryControllerBridge,
                         mFuseboxStateSupplier,
                         mSnackbarManager);
         if (mLastBrandedColorScheme != null) {
             mMediator.updateVisualsForState(mLastBrandedColorScheme);
         }
-        mModelList.setAttachmentUploadFailedListener(mMediator::onAttachmentUploadFailed);
     }
 
     public void destroy() {
@@ -217,10 +201,6 @@
             mTemplateUrlService.removeObserver(this);
         }
         mModelList.destroy();
-        if (mComposeboxQueryControllerBridge != null) {
-            mComposeboxQueryControllerBridge.destroy();
-            mComposeboxQueryControllerBridge = null;
-        }
         if (mViewportRectProvider != null) {
             mViewportRectProvider.destroy();
         }
@@ -244,6 +224,9 @@
      *     through the endInput() (valid -> valid). This is the case for tab switching.
      */
     public void beginInput(FuseboxSessionState session) {
+        // Abort early if there is no composebox.
+        if (session.getComposeboxQueryControllerBridge() == null) return;
+
         // We can't do inclusive check due to missing `isPhone()` case in `DeviceInfo`.
         // Additionally these values may change at runtime, e.g. if the user starts Chrome on phone
         // and moves to Android Auto.
@@ -271,12 +254,12 @@
             return;
         }
 
-        // Erase all attachments to avoid leaking them to other sessions when user switches tabs.
-        // TODO(crbug.com/474616308): remove when proper session persistence is available.
-        mModelList.clear();
+        // TODO(crbug.com/474616308): move to FuseboxSessionState.
+        AutocompleteController.getForProfile(assumeNonNull(session.getProfile()))
+                .setComposeboxQueryControllerBridge(session.getComposeboxQueryControllerBridge());
 
         mInput = session.getAutocompleteInput();
-        mMediator.beginInput(mInput);
+        mMediator.beginInput(session);
         FuseboxMetrics.notifyOmniboxSessionStarted();
     }
 
@@ -301,24 +284,6 @@
         }
     }
 
-    /** Returns the URL associated with the current AIM session. */
-    public void getAimUrl(GURL url, Callback<GURL> callback) {
-        if (mMediator == null) {
-            callback.onResult(GURL.emptyGURL());
-            return;
-        }
-        mMediator.getAimUrl(url, callback);
-    }
-
-    /** Returns the URL associated with the current image generation session. */
-    public void getImageGenerationUrl(GURL url, Callback<GURL> callback) {
-        if (mMediator == null) {
-            callback.onResult(GURL.emptyGURL());
-            return;
-        }
-        mMediator.getImageGenerationUrl(url, callback);
-    }
-
     public PropertyModel getModelForTesting() {
         return mModel;
     }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxCoordinatorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxCoordinatorUnitTest.java
index db5c389a..152a00b 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxCoordinatorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxCoordinatorUnitTest.java
@@ -10,7 +10,6 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.lenient;
@@ -83,7 +82,7 @@
 
     @Mock private AutocompleteController mAutocompleteController;
     @Mock private AutocompleteController.Natives mControllerJniMock;
-    @Mock private ComposeboxQueryControllerBridge.Natives mComposeboxController;
+    @Mock private ComposeboxQueryControllerBridge mComposebox;
     @Mock private FuseboxMediator mMediator;
     @Mock private TabModelSelector mTabModelSelector;
     @Mock private TabModel mTabModel;
@@ -110,8 +109,6 @@
 
     @Before
     public void setUp() {
-        ComposeboxQueryControllerBridgeJni.setInstanceForTesting(mComposeboxController);
-
         AutocompleteControllerJni.setInstanceForTesting(mControllerJniMock);
         lenient().doReturn(mAutocompleteController).when(mControllerJniMock).getForProfile(any());
 
@@ -151,7 +148,7 @@
     }
 
     private FuseboxSessionState createSession() {
-        return new FuseboxSessionState(mAutocompleteInput);
+        return new FuseboxSessionState(mAutocompleteInput, mComposebox, null);
     }
 
     @After
@@ -166,9 +163,6 @@
         // Start with a default state.
         mCoordinator.setMediatorForTesting(null);
 
-        doReturn(/* nativeInstance= */ 1L)
-                .when(mComposeboxController)
-                .init(any(Profile.class), any(ComposeboxQueryControllerBridge.class));
         mProfileSupplier.set(mProfile);
         assertNotNull(mCoordinator.getMediatorForTesting());
         assertNotEquals(mMediator, mCoordinator.getMediatorForTesting());
@@ -178,13 +172,12 @@
     @EnableFeatures(OmniboxFeatureList.OMNIBOX_MULTIMODAL_INPUT)
     public void testOnProfileAvailable_featureEnabled_noBridge() {
         // Start with a default state.
-        mCoordinator.setMediatorForTesting(null);
-
-        doReturn(/* nativeInstance= */ 0L)
-                .when(mComposeboxController)
-                .init(any(Profile.class), any(ComposeboxQueryControllerBridge.class));
+        mCoordinator.endInput();
+        mComposebox = null;
+        clearInvocations(mMediator);
         mProfileSupplier.set(mProfile);
-        assertNull(mCoordinator.getMediatorForTesting());
+        mCoordinator.beginInput(createSession());
+        verify(mMediator, never()).beginInput(any());
     }
 
     @Test
@@ -194,8 +187,6 @@
         mCoordinator.setMediatorForTesting(null);
 
         mProfileSupplier.set(mProfile);
-        verify(mComposeboxController, never())
-                .init(any(Profile.class), any(ComposeboxQueryControllerBridge.class));
         assertNull(mCoordinator.getMediatorForTesting());
     }
 
@@ -205,9 +196,6 @@
         // Start with a default state.
         mCoordinator.setMediatorForTesting(null);
 
-        doReturn(/* nativeInstance= */ 1L)
-                .when(mComposeboxController)
-                .init(any(Profile.class), any(ComposeboxQueryControllerBridge.class));
         mProfileSupplier.set(mProfile);
         assertNotNull(mCoordinator.getMediatorForTesting());
         assertNotEquals(mMediator, mCoordinator.getMediatorForTesting());
@@ -243,7 +231,7 @@
 
         // Mediator set by setUp().
         mCoordinator.beginInput(createSession());
-        verify(mMediator).beginInput(mAutocompleteInput);
+        verify(mMediator).beginInput(any());
 
         mCoordinator.endInput();
         verify(mMediator).endInput();
@@ -279,7 +267,7 @@
             mCoordinator.beginInput(createSession());
 
             boolean shouldBeVisible = supportedPageClassifications.contains(pageClass);
-            verify(mMediator, times(shouldBeVisible ? 1 : 0)).beginInput(mAutocompleteInput);
+            verify(mMediator, times(shouldBeVisible ? 1 : 0)).beginInput(any());
 
             mCoordinator.endInput();
         }
@@ -299,27 +287,24 @@
     @Test
     @EnableFeatures(OmniboxFeatureList.OMNIBOX_MULTIMODAL_INPUT)
     public void testNtpAiModeButtonPress() {
-        doReturn(/* nativeInstance= */ 1L)
-                .when(mComposeboxController)
-                .init(any(Profile.class), any(ComposeboxQueryControllerBridge.class));
         mProfileSupplier.set(mProfile);
         RobolectricUtil.runAllBackgroundAndUi();
         mAutocompleteInput.setRequestType(AutocompleteRequestType.AI_MODE);
 
         mCoordinator.beginInput(createSession());
-        verify(mMediator).beginInput(mAutocompleteInput);
+        verify(mMediator).beginInput(any());
     }
 
     @Test
     @EnableFeatures({OmniboxFeatureList.OMNIBOX_MULTIMODAL_INPUT})
     public void createImageButtonVisibility_regularProfile() {
-        doReturn(/* nativeInstance= */ 1L)
-                .when(mComposeboxController)
-                .init(any(Profile.class), any(ComposeboxQueryControllerBridge.class));
-        doReturn(true).when(mComposeboxController).isCreateImagesEligible(anyLong());
-
+        // Restart session with different eligibility.
+        mCoordinator.endInput();
+        doReturn(true).when(mComposebox).isCreateImagesEligible();
         OmniboxFeatures.sShowImageGenerationButtonInIncognito.setForTesting(false);
         mProfileSupplier.set(mProfile);
+        mCoordinator.beginInput(createSession());
+
         assertTrue(
                 mCoordinator
                         .getModelForTesting()
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxMediator.java
index 6c9c541..d881db6 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxMediator.java
@@ -32,7 +32,9 @@
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.omnibox.FuseboxSessionState;
 import org.chromium.chrome.browser.omnibox.R;
+import org.chromium.chrome.browser.omnibox.fusebox.FuseboxAttachmentModelList.FuseboxAttachmentChangeListener;
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxAttachmentRecyclerViewAdapter.FuseboxAttachmentType;
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxCoordinator.FuseboxState;
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxMetrics.AiModeActivationSource;
@@ -61,7 +63,6 @@
 import org.chromium.ui.modelutil.MVCListAdapter;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.permissions.AndroidPermissionDelegate;
-import org.chromium.url.GURL;
 
 import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
@@ -71,21 +72,24 @@
 
 /** Mediator for the Fusebox component. */
 @NullMarked
-public class FuseboxMediator {
+public class FuseboxMediator implements FuseboxAttachmentChangeListener {
     private final Context mContext;
     private final Profile mProfile;
     private final WindowAndroid mWindowAndroid;
     private final AndroidPermissionDelegate mPermissionDelegate;
     private final PropertyModel mModel;
     private final FuseboxPopup mPopup;
-    private final FuseboxAttachmentModelList mModelList;
+    private final FuseboxViewHolder mViewHolder;
     private final MonotonicObservableSupplier<TabModelSelector> mTabModelSelectorSupplier;
-    private final ComposeboxQueryControllerBridge mComposeboxQueryControllerBridge;
     private final SettableNonNullObservableSupplier<@FuseboxState Integer> mFuseboxStateSupplier;
     private final Callback<@AutocompleteRequestType Integer> mOnAutocompleteRequestTypeChanged =
             this::onAutocompleteRequestTypeChanged;
     private final SnackbarManager mSnackbarManager;
     private final Snackbar mAttachmentUploadFailedSnackbar;
+    private @BrandedColorScheme int mBrandedColorScheme = BrandedColorScheme.APP_DEFAULT;
+    private @Nullable AutocompleteInput mInput;
+    private @Nullable FuseboxAttachmentModelList mModelList;
+    private @Nullable ComposeboxQueryControllerBridge mComposeboxQueryControllerBridge;
     private final ListObserver<Void> mListObserver =
             new ListObserver<>() {
                 @Override
@@ -98,7 +102,6 @@
                     onAttachmentsChanged();
                 }
             };
-    private @Nullable AutocompleteInput mInput;
 
     FuseboxMediator(
             Context context,
@@ -106,9 +109,7 @@
             WindowAndroid windowAndroid,
             PropertyModel model,
             FuseboxViewHolder viewHolder,
-            FuseboxAttachmentModelList modelList,
             MonotonicObservableSupplier<TabModelSelector> tabModelSelectorSupplier,
-            ComposeboxQueryControllerBridge composeBoxQueryControllerBridge,
             SettableNonNullObservableSupplier<@FuseboxState Integer> fuseboxStateSupplier,
             SnackbarManager snackbarManager) {
         mContext = context;
@@ -117,9 +118,8 @@
         mPermissionDelegate = windowAndroid;
         mModel = model;
         mPopup = viewHolder.popup;
-        mModelList = modelList;
+        mViewHolder = viewHolder;
         mTabModelSelectorSupplier = tabModelSelectorSupplier;
-        mComposeboxQueryControllerBridge = composeBoxQueryControllerBridge;
         mFuseboxStateSupplier = fuseboxStateSupplier;
         mSnackbarManager = snackbarManager;
 
@@ -146,8 +146,23 @@
         mModel.set(
                 FuseboxProperties.POPUP_ATTACH_TAB_PICKER_VISIBLE,
                 ChromeFeatureList.sChromeItemPickerUi.isEnabled());
-        mModel.set(FuseboxProperties.POPUP_ATTACH_CAMERA_VISIBLE, true);
-        mModel.set(FuseboxProperties.POPUP_ATTACH_GALLERY_VISIBLE, true);
+    }
+
+    public void destroy() {
+        endInput();
+    }
+
+    @EnsuresNonNullIf(
+            value = {"mInput", "mModelList", "mComposeboxQueryControllerBridge"},
+            result = true)
+    private boolean isInInputSession() {
+        return mInput != null && mModelList != null && mComposeboxQueryControllerBridge != null;
+    }
+
+    private void setController(@Nullable ComposeboxQueryControllerBridge controller) {
+        mComposeboxQueryControllerBridge = controller;
+        if (mComposeboxQueryControllerBridge == null) return;
+
         mModel.set(
                 FuseboxProperties.POPUP_ATTACH_FILE_VISIBLE,
                 mComposeboxQueryControllerBridge.isPdfUploadEligible());
@@ -157,39 +172,57 @@
                 FuseboxProperties.POPUP_TOOL_CREATE_IMAGE_VISIBLE,
                 mComposeboxQueryControllerBridge.isCreateImagesEligible()
                         && (OmniboxFeatures.sShowImageGenerationButtonInIncognito.getValue()
-                                || !profile.isIncognitoBranded()));
-
-        mModelList.addObserver(mListObserver);
-        onAttachmentsChanged();
+                                || !mProfile.isIncognitoBranded()));
     }
 
-    public void destroy() {
-        mModelList.removeObserver(mListObserver);
-        endInput();
-    }
+    private void setModelList(@Nullable FuseboxAttachmentModelList modelList) {
+        if (mModelList == modelList) return;
 
-    @EnsuresNonNullIf("mInput")
-    private boolean isInInputSession() {
-        return mInput != null;
+        if (mModelList != null) {
+            mModelList.removeObserver(mListObserver);
+            mModelList.removeAttachmentChangeListener(this);
+            mModelList.setAttachmentUploadFailedListener(null);
+        }
+
+        mModelList = modelList;
+
+        if (mModelList != null) {
+            var adapter = mModelList.getAdapter();
+            mViewHolder.attachmentsView.setAdapter(adapter);
+            mModel.set(FuseboxProperties.ADAPTER, adapter);
+            mModelList.setAttachmentUploadFailedListener(this::onAttachmentUploadFailed);
+            mModelList.updateVisualsForState(mBrandedColorScheme);
+            mModelList.addAttachmentChangeListener(this);
+            mModelList.addObserver(mListObserver);
+            onAttachmentsChanged();
+        } else {
+            // need a safe fallback.
+            mViewHolder.attachmentsView.setAdapter(null);
+            mModel.set(FuseboxProperties.ADAPTER, null);
+            mModel.set(FuseboxProperties.ATTACHMENTS_VISIBLE, false);
+        }
     }
 
     /**
      * Called when the user begins interacting with the Omnibox.
      *
-     * @param input The input state for the new session. The input may be replaced without going
+     * @param session The input state for the new session. The input may be replaced without going
      *     through the endInput() (valid -> valid). This is the case for tab switching.
      */
-    /* package */ void beginInput(AutocompleteInput input) {
-        setAutocompleteInput(input);
+    /* package */ void beginInput(FuseboxSessionState session) {
+        setAutocompleteInput(session.getAutocompleteInput());
+        setController(session.getComposeboxQueryControllerBridge());
+        setModelList(session.getFuseboxAttachmentModelList());
         setToolbarVisible(true);
     }
 
     /** Called when the user stops interacting with the Omnibox. */
     /* package */ void endInput() {
-        mModelList.clear();
         mPopup.dismiss();
         setToolbarVisible(false);
         setAutocompleteInput(null);
+        setController(null);
+        setModelList(null);
     }
 
     private void setAutocompleteInput(@Nullable AutocompleteInput input) {
@@ -228,7 +261,9 @@
 
     /** Apply a variant of the branded color scheme to Fusebox UI elements */
     /*package */ void updateVisualsForState(@BrandedColorScheme int brandedColorScheme) {
+        mBrandedColorScheme = brandedColorScheme;
         mModel.set(FuseboxProperties.COLOR_SCHEME, brandedColorScheme);
+        if (mModelList == null) return;
         mModelList.updateVisualsForState(brandedColorScheme);
     }
 
@@ -304,25 +339,9 @@
                         && mInput.getRequestType() == AutocompleteRequestType.SEARCH);
     }
 
-    /**
-     * @param url The search URL to get the AIM analog of.
-     * @param callback The callback to run with the URL for the AIM service.
-     */
-    void getAimUrl(GURL url, Callback<GURL> callback) {
-        mComposeboxQueryControllerBridge.getAimUrl(url, callback);
-    }
-
-    /**
-     * @param url The search URL to get the Image generator analog of.
-     * @param callback The callback to run with the URL for the image generation service.
-     */
-    void getImageGenerationUrl(GURL url, Callback<GURL> callback) {
-        mComposeboxQueryControllerBridge.getImageGenerationUrl(url, callback);
-    }
-
     @VisibleForTesting
     void onToggleAttachmentsPopup() {
-        if (mPopup.isShowing()) {
+        if (!isInInputSession() || mPopup.isShowing()) {
             mPopup.dismiss();
         } else {
             updateModelForCurrentTab();
@@ -337,6 +356,7 @@
     }
 
     private void updateModelForCurrentTab() {
+        if (!isInInputSession()) return;
         var tabSelector = mTabModelSelectorSupplier.get();
         var shouldShowCurrentTab =
                 tabSelector != null
@@ -370,6 +390,7 @@
     }
 
     private void onAddCurrentTab(Tab tab) {
+        if (!isInInputSession()) return;
         FuseboxMetrics.notifyAttachmentButtonUsed(FuseboxAttachmentButtonType.CURRENT_TAB);
         maybeActivateAiMode(AiModeActivationSource.IMPLICIT);
 
@@ -417,6 +438,7 @@
     }
 
     private void onAttachmentsChanged() {
+        if (!isInInputSession()) return;
         mModel.set(FuseboxProperties.ATTACHMENTS_VISIBLE, !mModelList.isEmpty());
         mModel.set(
                 FuseboxProperties.POPUP_TOOL_CREATE_IMAGE_ENABLED,
@@ -425,6 +447,7 @@
     }
 
     private boolean areAttachmentsCompatibleWithCreateImage() {
+        if (!isInInputSession()) return false;
         int imageCount = 0;
         for (MVCListAdapter.ListItem listItem : mModelList) {
             if (listItem.type == FuseboxAttachmentType.ATTACHMENT_FILE) {
@@ -443,6 +466,7 @@
     @VisibleForTesting
     void onTabPickerClicked() {
         mPopup.dismiss();
+        if (!isInInputSession()) return;
         FuseboxMetrics.notifyAttachmentButtonUsed(FuseboxAttachmentButtonType.TAB_PICKER);
         if (isMaxAttachmentCountReached(FuseboxAttachmentType.ATTACHMENT_TAB)) return;
 
@@ -468,6 +492,7 @@
     }
 
     void onTabPickerResult(int resultCode, @Nullable Intent data) {
+        if (!isInInputSession()) return;
         if (resultCode != Activity.RESULT_OK || data == null || data.getExtras() == null) return;
         ArrayList<Integer> tabIds =
                 data.getIntegerArrayListExtra(ChromeItemPickerExtras.EXTRA_ATTACHMENT_TAB_IDS);
@@ -491,6 +516,7 @@
      */
     @VisibleForTesting
     public void updateCurrentlyAttachedTabs(Set<Integer> newlySelectedTabIds) {
+        if (!isInInputSession()) return;
         TabModelSelector tabModelSelector = mTabModelSelectorSupplier.get();
         if (tabModelSelector == null) return;
 
@@ -630,6 +656,7 @@
         mWindowAndroid.showCancelableIntent(
                 intent,
                 (resultCode, data) -> {
+                    if (!isInInputSession()) return;
                     if (resultCode != Activity.RESULT_OK || data == null) return;
 
                     try (var batchToken = mModelList.beginBatchEdit()) {
@@ -665,6 +692,7 @@
                 i,
                 (resultCode, data) -> {
                     if (resultCode != Activity.RESULT_OK || data == null) return;
+                    if (!isInInputSession()) return;
 
                     try (var batchToken = mModelList.beginBatchEdit()) {
                         var uris = extractUrisFromResult(data);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxMediatorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxMediatorUnitTest.java
index 1a01e62..d722b6d 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxMediatorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxMediatorUnitTest.java
@@ -61,6 +61,7 @@
 import org.chromium.base.test.RobolectricUtil;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
+import org.chromium.chrome.browser.omnibox.FuseboxSessionState;
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxAttachmentRecyclerViewAdapter.FuseboxAttachmentType;
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxCoordinator.FuseboxState;
@@ -186,12 +187,14 @@
                         mWindowAndroid,
                         mModel,
                         mViewHolder,
-                        mAttachments,
                         mTabModelSelectorSupplier,
-                        mComposeboxQueryControllerBridge,
                         mFuseboxStateSupplier,
                         mSnackbarManager);
-        mMediator.beginInput(mInput);
+        mMediator.beginInput(createSession());
+    }
+
+    private FuseboxSessionState createSession() {
+        return new FuseboxSessionState(mInput, mComposeboxQueryControllerBridge, mAttachments);
     }
 
     private void addTabAttachment(Tab tab) {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
index d49a47d..f1a35ca 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
@@ -118,6 +118,7 @@
             mDeferredIMEWindowInsetApplicationCallback;
     private final OmniboxSuggestionsDropdownEmbedder mEmbedder;
     private @Nullable AutocompleteInput mAutocompleteInput;
+    private @Nullable FuseboxSessionState mSessionState;
     private final boolean mForcePhoneStyleOmnibox;
     private final Callback<@ControlsPosition Integer> mToolbarPositionChangedCallback =
             this::onToolbarPositionChanged;
@@ -439,9 +440,10 @@
      *     through the endInput() (valid -> valid). This is the case for tab switching.
      */
     void beginInput(FuseboxSessionState session) {
-        boolean alreadyInInput = mAutocompleteInput != null;
+        boolean alreadyInInput = mSessionState != null;
         cancelAutocompleteRequests();
         setAutocompleteInput(session.getAutocompleteInput());
+        mSessionState = session;
 
         if (!alreadyInInput) {
             // Propagate the information about omnibox session state change to all the processors
@@ -495,7 +497,7 @@
      */
     void endInput() {
         // Session already inactive - stop.
-        if (mAutocompleteInput == null) return;
+        if (!isInInputSession()) return;
 
         // Propagate the information about omnibox session state change to all the processors first.
         // Processors need this for accounting purposes.
@@ -536,6 +538,7 @@
         // a consequence the omnibox is unfocused).
         clearSuggestions();
         setAutocompleteInput(null);
+        mSessionState = null;
     }
 
     private void setAutocompleteInput(@Nullable AutocompleteInput input) {
@@ -1172,6 +1175,8 @@
             long inputStart,
             boolean openInNewTab,
             boolean openInNewWindow) {
+        if (!isInInputSession()) return;
+
         try (TraceEvent e = TraceEvent.scoped("AutocompleteMediator.loadUrlFromOmniboxMatch")) {
             OmniboxMetrics.recordFocusToOpenTime(
                     System.currentTimeMillis()
@@ -1217,11 +1222,13 @@
                                 finalTransition);
                     };
 
-            switch (assumeNonNull(mAutocompleteInput).getRequestType()) {
+            switch (mAutocompleteInput.getRequestType()) {
                 case AutocompleteRequestType.AI_MODE ->
-                        mFuseboxCoordinator.getAimUrl(url, onUrlReady);
+                        assumeNonNull(mSessionState.getComposeboxQueryControllerBridge())
+                                .getAimUrl(url, onUrlReady);
                 case AutocompleteRequestType.IMAGE_GENERATION ->
-                        mFuseboxCoordinator.getImageGenerationUrl(url, onUrlReady);
+                        assumeNonNull(mSessionState.getComposeboxQueryControllerBridge())
+                                .getImageGenerationUrl(url, onUrlReady);
                 default -> onUrlReady.onResult(url);
             }
         }
@@ -1694,9 +1701,11 @@
      * @return Whether there is currently an active omnibox session. An active session is defined by
      *     the presence of an {@link AutocompleteInput} and the activity window having focus.
      */
-    @EnsuresNonNullIf("mAutocompleteInput")
+    @EnsuresNonNullIf(
+            value = {"mAutocompleteInput", "mSessionState"},
+            result = true)
     private boolean isInInputSession() {
-        return mAutocompleteInput != null && mActivityWindowFocused;
+        return mSessionState != null && mAutocompleteInput != null && mActivityWindowFocused;
     }
 
     @Override
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
index b9d240ad..da91c90 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
@@ -65,6 +65,7 @@
 import org.chromium.chrome.browser.omnibox.NewTabPageDelegate;
 import org.chromium.chrome.browser.omnibox.OmniboxMetrics;
 import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider;
+import org.chromium.chrome.browser.omnibox.fusebox.ComposeboxQueryControllerBridge;
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxCoordinator;
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxCoordinator.FuseboxState;
 import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
@@ -139,6 +140,7 @@
     private @Mock DeferredIMEWindowInsetApplicationCallback mDeferredImeCallback;
     private @Mock FuseboxCoordinator mFuseboxCoordinator;
     private @Mock PreloadingFeatureMap mPreloadingFeatureMap;
+    private @Mock ComposeboxQueryControllerBridge mComposeboxQueryControllerBridge;
     private @Captor ArgumentCaptor<OmniboxLoadUrlParams> mOmniboxLoadUrlParamsCaptor;
     private @Mock CachedZeroSuggestionsManager.OverridesForTesting
             mMockCachedZeroSuggestionsManager;
@@ -279,7 +281,7 @@
         autocompleteInput.setPageUrl(url);
         autocompleteInput.setPageTitle(title);
         autocompleteInput.setPageClassification(pageClassification);
-        return new FuseboxSessionState(autocompleteInput);
+        return new FuseboxSessionState(autocompleteInput, mComposeboxQueryControllerBridge, null);
     }
 
     private FuseboxSessionState createEmptySession() {
@@ -1685,7 +1687,7 @@
                             ((Callback<GURL>) invocation.getArgument(1)).onResult(url);
                             return null;
                         })
-                .when(mFuseboxCoordinator)
+                .when(mComposeboxQueryControllerBridge)
                 .getAimUrl(any(), any());
 
         AutocompleteMatch defaultMatch =
@@ -1730,7 +1732,7 @@
                             ((Callback<GURL>) invocation.getArgument(1)).onResult(url2);
                             return null;
                         })
-                .when(mFuseboxCoordinator)
+                .when(mComposeboxQueryControllerBridge)
                 .getImageGenerationUrl(any(), any());
 
         AutocompleteMatch defaultMatch =
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
index 524746b..2d993c6 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
@@ -583,7 +583,8 @@
 
     private void applyContentOffsetToModel(float contentOffset) {
         if (BrowserControlsUtils.isTopControlsRefactorOffsetEnabled()
-                && getControlsPosition() == ControlsPosition.TOP) {
+                && getControlsPosition() == ControlsPosition.TOP
+                && !mIsVisibilityManuallyControlled) {
             contentOffset = INVALID_CONTENT_OFFSET;
         }
         mModel.set(TopToolbarOverlayProperties.LEGACY_CONTENT_OFFSET, contentOffset);
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediatorTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediatorTest.java
index 3d3b33f9..1c56953 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediatorTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediatorTest.java
@@ -538,6 +538,42 @@
         ChromeFeatureList.TOP_CONTROLS_REFACTOR,
         ChromeFeatureList.TOP_CONTROLS_REFACTOR_V2
     })
+    public void testContentOffset_topControlsRefactorEnabled_manuallyControlled() {
+        mMediator.setVisibilityManuallyControlledForTesting(true);
+
+        int height = 150;
+        doReturn(height).when(mBrowserControlsStateProvider).getTopControlsHeight();
+        doReturn(ControlsPosition.TOP).when(mBrowserControlsStateProvider).getControlsPosition();
+        mBrowserControlsObserverCaptor.getValue().onControlsPositionChanged(ControlsPosition.TOP);
+
+        // When requestNewFrame is false, applyContentOffsetToModel receives getTopControlsHeight().
+        mBrowserControlsObserverCaptor
+                .getValue()
+                .onControlsOffsetChanged(
+                        0, 0, false, 0, 0, false, /* requestNewFrame= */ false, false);
+        assertEquals(
+                (float) height,
+                mModel.get(TopToolbarOverlayProperties.LEGACY_CONTENT_OFFSET),
+                MathUtils.EPSILON);
+
+        // When requestNewFrame is true, applyContentOffsetToModel receives getContentOffset().
+        int contentOffset = 200;
+        doReturn(contentOffset).when(mBrowserControlsStateProvider).getContentOffset();
+        mBrowserControlsObserverCaptor
+                .getValue()
+                .onControlsOffsetChanged(
+                        0, 0, false, 0, 0, false, /* requestNewFrame= */ true, false);
+        assertEquals(
+                (float) contentOffset,
+                mModel.get(TopToolbarOverlayProperties.LEGACY_CONTENT_OFFSET),
+                MathUtils.EPSILON);
+    }
+
+    @Test
+    @EnableFeatures({
+        ChromeFeatureList.TOP_CONTROLS_REFACTOR,
+        ChromeFeatureList.TOP_CONTROLS_REFACTOR_V2
+    })
     public void testContentOffset_topControlsRefactorEnabled_ControlsAtBottom() {
         float height = 700.0f;
         mMediator.setViewportHeight(height);
diff --git a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
index 22a7dd83..f68709b 100644
--- a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
@@ -3804,10 +3804,10 @@
 }
 
 // Creates a browser with four tabs. The first two tabs belong in Tab Group 1.
-// Dragging the collapsed group header of Tab Group 1 will result in Tab Group 1
-// expanding.
+// Dragging the collapsed group header of Tab Group 1 will not expand the
+// group.
 IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
-                       DragCollapsedGroupHeaderExpandsGroup) {
+                       DragCollapsedGroupHeaderRemainsCollapsed) {
   ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
 
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
@@ -3825,14 +3825,14 @@
   ASSERT_EQ(4, model->count());
   ASSERT_EQ(2u, group_model->GetTabGroup(group)->ListTabs().length());
 
-  // Drag group1, this should expand the group.
+  // Drag group1, group should remain collapsed.
   ASSERT_TRUE(PressInputAtCenter(tab_strip->group_header(group)));
   ASSERT_TRUE(DragInputToCenter(tab_strip->tab_at(1)));
   ASSERT_TRUE(TabDragController::IsActive());
-  EXPECT_FALSE(model->IsGroupCollapsed(group));
+  EXPECT_TRUE(model->IsGroupCollapsed(group));
   ASSERT_TRUE(ReleaseInput());
   StopAnimating(tab_strip);
-  EXPECT_FALSE(model->IsGroupCollapsed(group));
+  EXPECT_TRUE(model->IsGroupCollapsed(group));
 }
 
 #if BUILDFLAG(IS_MAC)
diff --git a/chrome/browser/ui/views/tabs/tab_group_header_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_group_header_interactive_uitest.cc
index 52f45d8..7b7b2220 100644
--- a/chrome/browser/ui/views/tabs/tab_group_header_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_header_interactive_uitest.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/test/bind.h"
 #include "chrome/browser/data_sharing/data_sharing_service_factory.h"
 #include "chrome/browser/tab_group_sync/tab_group_sync_service_factory.h"
 #include "chrome/browser/ui/browser_element_identifiers.h"
@@ -29,6 +30,7 @@
 #include "ui/gfx/codec/png_codec.h"
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/gfx/image/image_unittest_util.h"
+#include "ui/views/interaction/element_tracker_views.h"
 #include "ui/views/interaction/polling_view_observer.h"
 
 class TabGroupHeaderInteractiveUiTest
@@ -123,3 +125,28 @@
                         ->ShouldShowAttentionIndicator());
       }));
 }
+
+IN_PROC_BROWSER_TEST_F(TabGroupHeaderInteractiveUiTest, DragCollapsedGroup) {
+  CreateTabGroup({CreateTab()});
+
+  RunTestSequence(
+      WaitForShow(kTabGroupHeaderElementId), FinishTabstripAnimations(),
+      PollViewProperty(kTabGroupCollapsedState, kTabGroupHeaderElementId,
+                       &TabGroupHeader::is_collapsed_for_testing),
+      // Collapse the group
+      MoveMouseTo(kTabGroupHeaderElementId), ClickMouse(ui_controls::LEFT),
+      WaitForState(kTabGroupCollapsedState, true), FinishTabstripAnimations(),
+      // Drag the group header. We drag it a bit to the right.
+      MoveMouseTo(kTabGroupHeaderElementId),
+      DragMouseTo(kTabGroupHeaderElementId,
+                  base::BindLambdaForTesting([](ui::TrackedElement* el) {
+                    return el->AsA<views::TrackedElementViews>()
+                               ->view()
+                               ->GetBoundsInScreen()
+                               .CenterPoint() +
+                           gfx::Vector2d(50, 0);
+                  })),
+      // Verify it is still collapsed
+      CheckViewProperty(kTabGroupHeaderElementId,
+                        &TabGroupHeader::is_collapsed_for_testing, true));
+}
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 670e7c95..be0b9d0 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -1505,6 +1505,13 @@
       // If the tab that is about to be selected is in a collapsed group,
       // automatically expand the group.
       if (IsGroupCollapsed(new_group)) {
+        // If the group is being dragged, do not expand it.
+        if (drag_context_->GetDragController() &&
+            drag_context_->GetDragController()->group_header_id() ==
+                new_group) {
+          continue;
+        }
+
         ToggleTabGroupCollapsedState(
             new_group, ToggleTabGroupCollapsedStateOrigin::kTabsSelected);
       }
diff --git a/chrome/browser/ui/views/toolbar/DEPS b/chrome/browser/ui/views/toolbar/DEPS
new file mode 100644
index 0000000..f503dd6
--- /dev/null
+++ b/chrome/browser/ui/views/toolbar/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/browser_apis/ui_controllers/toolbar",
+]
diff --git a/chrome/browser/ui/views/toolbar/webui_reload_control.cc b/chrome/browser/ui/views/toolbar/webui_reload_control.cc
index 71c37d2..e0c1e1a 100644
--- a/chrome/browser/ui/views/toolbar/webui_reload_control.cc
+++ b/chrome/browser/ui/views/toolbar/webui_reload_control.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/ui/views/toolbar/webui_toolbar_web_view.h"
 #include "chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom.h"
 #include "ui/base/window_open_disposition_utils.h"
 #include "ui/views/controls/menu/menu_runner.h"
 #include "ui/views/widget/widget.h"
@@ -95,7 +96,7 @@
 }
 
 void WebUIReloadControl::UpdateState() {
-  auto state = browser_controls_api::mojom::ReloadControlState::New();
+  auto state = toolbar_ui_api::mojom::ReloadControlState::New();
   state->is_devtools_connected = is_dev_tools_connected_;
   state->is_navigation_loading = (mode_ == ReloadControl::Mode::kStop);
   state->is_context_menu_visible = menu_runner_->IsRunning();
diff --git a/chrome/browser/ui/views/toolbar/webui_split_tabs_control.cc b/chrome/browser/ui/views/toolbar/webui_split_tabs_control.cc
index 8cd97a0..9e16224 100644
--- a/chrome/browser/ui/views/toolbar/webui_split_tabs_control.cc
+++ b/chrome/browser/ui/views/toolbar/webui_split_tabs_control.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui_data_source.h"
@@ -57,13 +58,12 @@
 }
 
 void WebUISplitTabsControl::HandleContextMenu(
-    browser_controls_api::mojom::ContextMenuType menu_type,
+    toolbar_ui_api::mojom::ContextMenuType menu_type,
     const gfx::Point& screen_location,
     ui::mojom::MenuSourceType source_type) {
   BrowserWindowInterface* browser = toolbar_view_->browser_;
   current_menu_type_ = menu_type;
-  if (menu_type ==
-      browser_controls_api::mojom::ContextMenuType::kSplitTabsAction) {
+  if (menu_type == toolbar_ui_api::mojom::ContextMenuType::kSplitTabsAction) {
     // Only show "Separate Views" menu if actually in split.
     auto* tab_strip_model = browser->GetTabStripModel();
     if (!tab_strip_model || !tab_strip_model->GetActiveTab() ||
@@ -77,7 +77,7 @@
         tab_strip_model, SplitTabMenuModel::MenuSource::kToolbarButton);
     RunMenuAt(screen_location.x(), screen_location.y(), source_type);
   } else if (menu_type ==
-             browser_controls_api::mojom::ContextMenuType::kSplitTabsContext) {
+             toolbar_ui_api::mojom::ContextMenuType::kSplitTabsContext) {
     Browser* actual_browser =
         chrome::FindBrowserWithWindow(browser->GetWindow()->GetNativeWindow());
     if (actual_browser) {
@@ -124,7 +124,7 @@
 }
 
 void WebUISplitTabsControl::UpdateVisibility(
-    const browser_controls_api::mojom::SplitTabsControlState* state) {
+    const toolbar_ui_api::mojom::SplitTabsControlState* state) {
   bool should_be_visible = state->is_pinned || state->is_current_tab_split;
 
   if (should_be_visible != is_visible_) {
@@ -134,13 +134,13 @@
 }
 
 void WebUISplitTabsControl::UpdateState() {
-  auto state = browser_controls_api::mojom::SplitTabsControlState::New();
+  auto state = toolbar_ui_api::mojom::SplitTabsControlState::New();
   auto s = webui_toolbar::ComputeTabSplitStatus(toolbar_view_->browser_);
   state->is_current_tab_split = s.is_split;
   state->location = s.location;
   state->is_pinned = webui_toolbar::IsButtonPinned(
       toolbar_view_->browser_,
-      browser_controls_api::mojom::ToolbarButtonType::kSplitTabs);
+      toolbar_ui_api::mojom::ToolbarButtonType::kSplitTabs);
   state->is_context_menu_visible = menu_runner_ && menu_runner_->IsRunning();
   UpdateVisibility(state.get());
   toolbar_view_->OnSplitTabsControlStateChanged(std::move(state));
diff --git a/chrome/browser/ui/views/toolbar/webui_split_tabs_control.h b/chrome/browser/ui/views/toolbar/webui_split_tabs_control.h
index d42527e..53edf034 100644
--- a/chrome/browser/ui/views/toolbar/webui_split_tabs_control.h
+++ b/chrome/browser/ui/views/toolbar/webui_split_tabs_control.h
@@ -9,9 +9,10 @@
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/webui/webui_toolbar/utils/split_tabs_utils.h"
 #include "components/browser_apis/browser_controls/browser_controls_api.mojom.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom.h"
 #include "components/prefs/pref_member.h"
 #include "ui/base/models/menu_model.h"
-#include "ui/base/mojom/menu_source_type.mojom-forward.h"
+#include "ui/base/mojom/menu_source_type.mojom.h"
 #include "ui/views/controls/menu/menu_runner.h"
 
 class WebUIToolbarWebView;
@@ -33,7 +34,7 @@
   bool IsVisible() const;
 
   // Handles context menu requests from the WebUI.
-  void HandleContextMenu(browser_controls_api::mojom::ContextMenuType menu_type,
+  void HandleContextMenu(toolbar_ui_api::mojom::ContextMenuType menu_type,
                          const gfx::Point& screen_location,
                          ui::mojom::MenuSourceType source);
 
@@ -51,7 +52,7 @@
                            CheckSplitTabsButtonSourceType);
 
   void UpdateVisibility(
-      const browser_controls_api::mojom::SplitTabsControlState* state);
+      const toolbar_ui_api::mojom::SplitTabsControlState* state);
   void UpdateState();
   void RunMenuAt(int x, int y, ui::mojom::MenuSourceType source_type);
 
@@ -59,8 +60,8 @@
   BooleanPrefMember pin_state_;
   bool is_visible_ = false;
 
-  browser_controls_api::mojom::ContextMenuType current_menu_type_ =
-      browser_controls_api::mojom::ContextMenuType::kUnspecified;
+  toolbar_ui_api::mojom::ContextMenuType current_menu_type_ =
+      toolbar_ui_api::mojom::ContextMenuType::kUnspecified;
   ui::mojom::MenuSourceType last_source_type_for_testing_ =
       ui::mojom::MenuSourceType::kNone;
 
diff --git a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc
index 8b4dec3d..ccf3302 100644
--- a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc
+++ b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc
@@ -136,9 +136,9 @@
                               base::Unretained(this)))) {
   base::trace_event::EmitNamedTrigger("webui-toolbar-constructor");
   last_queued_state_.split_tabs_control_state =
-      browser_controls_api::mojom::SplitTabsControlState::New();
+      toolbar_ui_api::mojom::SplitTabsControlState::New();
   last_queued_state_.reload_control_state =
-      browser_controls_api::mojom::ReloadControlState::New();
+      toolbar_ui_api::mojom::ReloadControlState::New();
   last_queued_state_.layout_constants_version = 0;
   if (auto* manager = InitialWebUIWindowMetricsManager::From(browser_)) {
     manager->OnReloadButtonCreated();
@@ -218,7 +218,7 @@
 }
 
 void WebUIToolbarWebView::HandleContextMenu(
-    browser_controls_api::mojom::ContextMenuType menu_type,
+    toolbar_ui_api::mojom::ContextMenuType menu_type,
     gfx::Point viewport_coordinate_css_pixels,
     ui::mojom::MenuSourceType source) {
   CHECK(web_view_);
@@ -234,14 +234,14 @@
           .OffsetFromOrigin();
 
   switch (menu_type) {
-    case browser_controls_api::mojom::ContextMenuType::kReload:
+    case toolbar_ui_api::mojom::ContextMenuType::kReload:
       reload_control_.HandleContextMenu(GetWidget(), screen_location, source);
       break;
-    case browser_controls_api::mojom::ContextMenuType::kSplitTabsAction:
-    case browser_controls_api::mojom::ContextMenuType::kSplitTabsContext:
+    case toolbar_ui_api::mojom::ContextMenuType::kSplitTabsAction:
+    case toolbar_ui_api::mojom::ContextMenuType::kSplitTabsContext:
       split_tabs_control_.HandleContextMenu(menu_type, screen_location, source);
       break;
-    case browser_controls_api::mojom::ContextMenuType::kUnspecified:
+    case toolbar_ui_api::mojom::ContextMenuType::kUnspecified:
       NOTREACHED() << "Unexpected ClickDispositionFlag::kUnspecified.";
   }
 }
@@ -261,25 +261,28 @@
   return &reload_control_;
 }
 
-browser_controls_api::BrowserControlsService::Delegate*
-WebUIToolbarWebView::GetDelegate() {
+browser_controls_api::BrowserControlsService::BrowserControlsServiceDelegate*
+WebUIToolbarWebView::GetBrowserControlsDelegate() {
   return this;
 }
 
-std::unique_ptr<browser_controls_api::NavigationControlsStateFetcher>
+toolbar_ui_api::ToolbarUIService::ToolbarUIServiceDelegate*
+WebUIToolbarWebView::GetToolbarUIServiceDelegate() {
+  return this;
+}
+
+std::unique_ptr<toolbar_ui_api::NavigationControlsStateFetcher>
 WebUIToolbarWebView::GetNavigationControlsStateFetcher() {
-  return std::make_unique<
-      browser_controls_api::NavigationControlsStateFetcherImpl>(
+  return std::make_unique<toolbar_ui_api::NavigationControlsStateFetcherImpl>(
       base::BindRepeating(&WebUIToolbarWebView::GetNavigationControlsState,
                           base::Unretained(this)));
 }
 
-browser_controls_api::mojom::NavigationControlsStatePtr
+toolbar_ui_api::mojom::NavigationControlsStatePtr
 WebUIToolbarWebView::GetNavigationControlsState() {
   return last_queued_state_.Clone();
 }
 
-
 void WebUIToolbarWebView::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
   if (!navigation_handle->IsInPrimaryMainFrame()) {
@@ -419,7 +422,7 @@
 }
 
 void WebUIToolbarWebView::OnReloadControlStateChanged(
-    browser_controls_api::mojom::ReloadControlStatePtr state) {
+    toolbar_ui_api::mojom::ReloadControlStatePtr state) {
   if (*state != *last_queued_state_.reload_control_state) {
     last_queued_state_.reload_control_state = std::move(state);
     PostPushNavigationState();
@@ -427,7 +430,7 @@
 }
 
 void WebUIToolbarWebView::OnSplitTabsControlStateChanged(
-    browser_controls_api::mojom::SplitTabsControlStatePtr state) {
+    toolbar_ui_api::mojom::SplitTabsControlStatePtr state) {
   if (*state != *last_queued_state_.split_tabs_control_state) {
     last_queued_state_.split_tabs_control_state = std::move(state);
     PostPushNavigationState();
diff --git a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.h b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.h
index 151a5f92..27eafa10 100644
--- a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.h
+++ b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.h
@@ -13,8 +13,11 @@
 #include "chrome/browser/ui/views/toolbar/webui_split_tabs_control.h"
 #include "chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher.h"
 #include "chrome/browser/ui/webui/webui_toolbar/browser_controls_service.h"
+#include "chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service.h"
 #include "chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.h"
 #include "chrome/common/webui_url_constants.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "ui/base/metadata/metadata_header_macros.h"
@@ -33,7 +36,9 @@
 class WebUIToolbarWebView
     : public views::View,
       public content::WebContentsObserver,
-      public browser_controls_api::BrowserControlsService::Delegate,
+      public toolbar_ui_api::ToolbarUIService::ToolbarUIServiceDelegate,
+      public browser_controls_api::BrowserControlsService::
+          BrowserControlsServiceDelegate,
       public WebUIToolbarUI::DependencyProvider {
   METADATA_HEADER(WebUIToolbarWebView, views::View)
 
@@ -52,16 +57,20 @@
   WebUILocationBar* GetLocationBar() { return location_bar_.get(); }
 
   // WebUIToolbarUI::DependencyProvider:
-  browser_controls_api::BrowserControlsService::Delegate* GetDelegate()
-      override;
-  std::unique_ptr<browser_controls_api::NavigationControlsStateFetcher>
+  browser_controls_api::BrowserControlsService::BrowserControlsServiceDelegate*
+  GetBrowserControlsDelegate() override;
+  toolbar_ui_api::ToolbarUIService::ToolbarUIServiceDelegate*
+  GetToolbarUIServiceDelegate() override;
+  std::unique_ptr<toolbar_ui_api::NavigationControlsStateFetcher>
   GetNavigationControlsStateFetcher() override;
 
-  // BrowserControlsService::BrowserControlsServiceDelegate:
-  void HandleContextMenu(browser_controls_api::mojom::ContextMenuType menu_type,
+  // ToolbarUIService::ToolbarUIServiceDelegate:
+  void HandleContextMenu(toolbar_ui_api::mojom::ContextMenuType menu_type,
                          gfx::Point viewport_coordinate_css_pixels,
                          ui::mojom::MenuSourceType source) override;
   void OnPageInitialized() override;
+
+  // BrowserControlsService::BrowserControlsServiceDelegate:
   void PermitLaunchUrl() override;
 
   // views::View:
@@ -93,7 +102,7 @@
   friend WebUIReloadControl;
   friend WebUISplitTabsControl;
 
-  browser_controls_api::mojom::NavigationControlsStatePtr
+  toolbar_ui_api::mojom::NavigationControlsStatePtr
   GetNavigationControlsState();
 
   // Reloads the WebUI toolbar to recover from crashes or unresponsiveness.
@@ -118,14 +127,14 @@
 
   // Called by friended controls to push state.
   void OnReloadControlStateChanged(
-      browser_controls_api::mojom::ReloadControlStatePtr state);
+      toolbar_ui_api::mojom::ReloadControlStatePtr state);
   void OnSplitTabsControlStateChanged(
-      browser_controls_api::mojom::SplitTabsControlStatePtr state);
+      toolbar_ui_api::mojom::SplitTabsControlStatePtr state);
 
   void OnTouchUiChanged();
   void PostPushNavigationState();
   void PushNavigationState(uint64_t state_generation);
-  browser_controls_api::mojom::NavigationControlsState last_queued_state_;
+  toolbar_ui_api::mojom::NavigationControlsState last_queued_state_;
   uint64_t current_state_generation_ = 0;
 
   InitializationState initialization_state_ =
diff --git a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc
index 3d7b791..c8d887f 100644
--- a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc
+++ b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc
@@ -38,6 +38,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom.h"
 #include "components/prefs/pref_service.h"
 #include "components/viz/common/frame_sinks/copy_output_result.h"
 #include "content/public/browser/javascript_dialog_manager.h"
@@ -338,7 +339,7 @@
   // Show reload button context menu.
   webui_toolbar_view->GetReloadControl()->SetDevToolsStatus(true);
   webui_toolbar_view->HandleContextMenu(
-      browser_controls_api::mojom::ContextMenuType::kReload,
+      toolbar_ui_api::mojom::ContextMenuType::kReload,
       element->GetScreenBounds().bottom_right(),
       ui::mojom::MenuSourceType::kMouse);
 
@@ -391,7 +392,7 @@
 
   // Show context menu.
   split_tabs_control->HandleContextMenu(
-      browser_controls_api::mojom::ContextMenuType::kSplitTabsContext,
+      toolbar_ui_api::mojom::ContextMenuType::kSplitTabsContext,
       element->GetScreenBounds().bottom_right(),
       ui::mojom::MenuSourceType::kMouse);
 
diff --git a/chrome/browser/ui/waap/initial_webui_browsertest.cc b/chrome/browser/ui/waap/initial_webui_browsertest.cc
index dbc67be..9c870ed 100644
--- a/chrome/browser/ui/waap/initial_webui_browsertest.cc
+++ b/chrome/browser/ui/waap/initial_webui_browsertest.cc
@@ -53,19 +53,23 @@
 
   // This might blow up in the future. We are implicitly assuming that the
   // delegate isn't going to be used in this test.
-  browser_controls_api::BrowserControlsService::Delegate* GetDelegate()
-      override {
+  browser_controls_api::BrowserControlsService::BrowserControlsServiceDelegate*
+  GetBrowserControlsDelegate() override {
     return nullptr;
   }
 
-  std::unique_ptr<browser_controls_api::NavigationControlsStateFetcher>
+  toolbar_ui_api::ToolbarUIService::ToolbarUIServiceDelegate*
+  GetToolbarUIServiceDelegate() override {
+    return nullptr;
+  }
+
+  std::unique_ptr<toolbar_ui_api::NavigationControlsStateFetcher>
   GetNavigationControlsStateFetcher() override {
-    return std::make_unique<
-        browser_controls_api::NavigationControlsStateFetcherImpl>(
+    return std::make_unique<toolbar_ui_api::NavigationControlsStateFetcherImpl>(
         base::BindLambdaForTesting([]() {
-          return browser_controls_api::mojom::NavigationControlsState::New(
-              browser_controls_api::mojom::ReloadControlState::New(),
-              browser_controls_api::mojom::SplitTabsControlState::New(),
+          return toolbar_ui_api::mojom::NavigationControlsState::New(
+              toolbar_ui_api::mojom::ReloadControlState::New(),
+              toolbar_ui_api::mojom::SplitTabsControlState::New(),
               /*layout_constants_version=*/0);
         }));
   }
diff --git a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
index f176641..f1716f9 100644
--- a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
@@ -110,6 +110,7 @@
 
   void StartDistillation(dom_distiller::DomDistillerService* service,
                          content::WebContents* contents) {
+    start_time_ = base::TimeTicks::Now();
     // If existing distillation request, cancel it. This removes delegate as
     // observer of previous request and allow it to observe new request.
     viewer_handle_.reset();
@@ -125,6 +126,10 @@
   // dom_distiller::ViewRequestDelegate:
   void OnArticleReady(
       const dom_distiller::DistilledArticleProto* article_proto) override {
+    CHECK(!start_time_.is_null());
+    base::UmaHistogramMediumTimes(
+        "Accessibility.ReadAnything.TimeFromStartDistillationToOnArticleReady",
+        base::TimeTicks::Now() - start_time_);
     handler_->ProcessDistilledArticle(article_proto);
     viewer_handle_.reset();
   }
@@ -137,6 +142,7 @@
  private:
   raw_ptr<ReadAnythingUntrustedPageHandler> handler_;
   std::unique_ptr<dom_distiller::ViewerHandle> viewer_handle_;
+  base::TimeTicks start_time_;
 };
 
 namespace {
diff --git a/chrome/browser/ui/webui/webui_toolbar/BUILD.gn b/chrome/browser/ui/webui/webui_toolbar/BUILD.gn
index cf522691..b368bd9 100644
--- a/chrome/browser/ui/webui/webui_toolbar/BUILD.gn
+++ b/chrome/browser/ui/webui/webui_toolbar/BUILD.gn
@@ -11,6 +11,7 @@
 source_set("webui_toolbar") {
   sources = [
     "browser_controls_service.h",
+    "toolbar_ui_service.h",
     "webui_toolbar_ui.h",
   ]
   public_deps = [
@@ -19,6 +20,7 @@
     "//chrome/common:constants",
     "//components/browser_apis/browser_controls:mojom",
     "//components/browser_apis/browser_controls:mojom_data_model",
+    "//components/browser_apis/ui_controllers/toolbar:mojom",
     "//content/public/browser",
     "//ui/webui/resources/js/tracked_element:mojo_bindings",
     "//ui/webui/tracked_element",
@@ -34,6 +36,7 @@
 source_set("impl") {
   sources = [
     "browser_controls_service.cc",
+    "toolbar_ui_service.cc",
     "webui_toolbar_layout_css_helper.cc",
     "webui_toolbar_layout_css_helper.h",
     "webui_toolbar_ui.cc",
@@ -70,6 +73,7 @@
   testonly = true
   sources = [
     "browser_controls_service_unittest.cc",
+    "toolbar_ui_service_unittest.cc",
     "webui_toolbar_layout_css_helper_unittest.cc",
     "webui_toolbar_ui_unittest.cc",
   ]
@@ -89,6 +93,7 @@
     "//chrome/test:test_support",
     "//components/browser_apis/browser_controls:mojom",
     "//components/browser_apis/browser_controls:mojom_data_model",
+    "//components/browser_apis/ui_controllers/toolbar:mojom",
     "//content/test:test_support",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/chrome/browser/ui/webui/webui_toolbar/DEPS b/chrome/browser/ui/webui/webui_toolbar/DEPS
index b4ebea21..1bed516fa 100644
--- a/chrome/browser/ui/webui/webui_toolbar/DEPS
+++ b/chrome/browser/ui/webui/webui_toolbar/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
   "+components/browser_apis/browser_controls",
+  "+components/browser_apis/ui_controllers/toolbar",
 ]
\ No newline at end of file
diff --git a/chrome/browser/ui/webui/webui_toolbar/adapters/BUILD.gn b/chrome/browser/ui/webui/webui_toolbar/adapters/BUILD.gn
index e97f3636..181b942f 100644
--- a/chrome/browser/ui/webui/webui_toolbar/adapters/BUILD.gn
+++ b/chrome/browser/ui/webui/webui_toolbar/adapters/BUILD.gn
@@ -11,6 +11,7 @@
     "//chrome/browser/ui/webui/webui_toolbar/utils",
     "//components/browser_apis/browser_controls:mojom",
     "//components/browser_apis/browser_controls:mojom_data_model",
+    "//components/browser_apis/ui_controllers/toolbar:mojom_data_model",
     "//ui/base:types",
   ]
 }
@@ -30,5 +31,6 @@
     "//chrome/browser/ui/tabs",
     "//chrome/browser/ui/webui/webui_toolbar/utils",
     "//components/browser_apis/browser_controls:mojom_data_model",
+    "//components/browser_apis/ui_controllers/toolbar:mojom_data_model",
   ]
 }
diff --git a/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter.h b/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter.h
index ed8fc74..dd99820a 100644
--- a/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter.h
+++ b/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter.h
@@ -23,7 +23,8 @@
   virtual void CreateNewSplitTab() = 0;
   // These should probably be pulled to their own adapter.
   virtual webui_toolbar::TabSplitStatus ComputeSplitTabStatus() = 0;
-  virtual bool IsButtonPinned(mojom::ToolbarButtonType type) = 0;
+  virtual bool IsButtonPinned(
+      toolbar_ui_api::mojom::ToolbarButtonType type) = 0;
 };
 
 }  // namespace browser_controls_api
diff --git a/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.cc b/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.cc
index 1614780d..bbc611a 100644
--- a/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.cc
+++ b/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.cc
@@ -43,7 +43,8 @@
   return webui_toolbar::ComputeTabSplitStatus(&browser_.get());
 }
 
-bool BrowserControlsAdapterImpl::IsButtonPinned(mojom::ToolbarButtonType type) {
+bool BrowserControlsAdapterImpl::IsButtonPinned(
+    toolbar_ui_api::mojom::ToolbarButtonType type) {
   return webui_toolbar::IsButtonPinned(&browser_.get(), type);
 }
 
diff --git a/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.h b/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.h
index 5687a7f..6e52975 100644
--- a/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.h
+++ b/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.h
@@ -28,7 +28,7 @@
   void Stop() override;
   void CreateNewSplitTab() override;
   webui_toolbar::TabSplitStatus ComputeSplitTabStatus() override;
-  bool IsButtonPinned(mojom::ToolbarButtonType type) override;
+  bool IsButtonPinned(toolbar_ui_api::mojom::ToolbarButtonType type) override;
 
  private:
   // Not owned.
diff --git a/chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher.h b/chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher.h
index aaa3e98..1d11a3d 100644
--- a/chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher.h
+++ b/chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher.h
@@ -5,9 +5,9 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_WEBUI_TOOLBAR_ADAPTERS_NAVIGATION_CONTROLS_STATE_FETCHER_H_
 #define CHROME_BROWSER_UI_WEBUI_WEBUI_TOOLBAR_ADAPTERS_NAVIGATION_CONTROLS_STATE_FETCHER_H_
 
-#include "components/browser_apis/browser_controls/browser_controls_api.mojom.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom.h"
 
-namespace browser_controls_api {
+namespace toolbar_ui_api {
 
 // An adapter which fetches and combines the current toolbar control states.
 class NavigationControlsStateFetcher {
@@ -17,6 +17,6 @@
   virtual mojom::NavigationControlsStatePtr GetNavigationControlsState() = 0;
 };
 
-}  // namespace browser_controls_api
+}  // namespace toolbar_ui_api
 
 #endif  // CHROME_BROWSER_UI_WEBUI_WEBUI_TOOLBAR_ADAPTERS_NAVIGATION_CONTROLS_STATE_FETCHER_H_
diff --git a/chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher_impl.cc b/chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher_impl.cc
index 13143b3d..ba707731 100644
--- a/chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher_impl.cc
+++ b/chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher_impl.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher_impl.h"
 
-namespace browser_controls_api {
+namespace toolbar_ui_api {
 
 NavigationControlsStateFetcherImpl::NavigationControlsStateFetcherImpl(
     CallbackType state_fetcher)
@@ -18,4 +18,4 @@
   return state_fetcher_.Run();
 }
 
-}  // namespace browser_controls_api
+}  // namespace toolbar_ui_api
diff --git a/chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher_impl.h b/chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher_impl.h
index 182b46cc..aea8b53 100644
--- a/chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher_impl.h
+++ b/chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher_impl.h
@@ -5,9 +5,10 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_WEBUI_TOOLBAR_ADAPTERS_NAVIGATION_CONTROLS_STATE_FETCHER_IMPL_H_
 #define CHROME_BROWSER_UI_WEBUI_WEBUI_TOOLBAR_ADAPTERS_NAVIGATION_CONTROLS_STATE_FETCHER_IMPL_H_
 
+#include "base/functional/callback.h"
 #include "chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher.h"
 
-namespace browser_controls_api {
+namespace toolbar_ui_api {
 
 // State fetcher using a simple repeating callback.
 class NavigationControlsStateFetcherImpl
@@ -25,6 +26,6 @@
   CallbackType state_fetcher_;
 };
 
-}  // namespace browser_controls_api
+}  // namespace toolbar_ui_api
 
 #endif  // CHROME_BROWSER_UI_WEBUI_WEBUI_TOOLBAR_ADAPTERS_NAVIGATION_CONTROLS_STATE_FETCHER_IMPL_H_
diff --git a/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.cc b/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.cc
index 4a99fe2b..c955014 100644
--- a/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.cc
+++ b/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.cc
@@ -9,22 +9,15 @@
 
 #include "base/check.h"
 #include "base/functional/bind.h"
-#include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/time/time.h"
 #include "chrome/browser/ui/webui/metrics_reporter/metrics_reporter.h"
-#include "chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter.h"
-#include "chrome/browser/ui/webui/webui_toolbar/utils/split_tabs_utils.h"
 #include "components/browser_apis/browser_controls/browser_controls_api.mojom.h"
-#include "content/public/browser/context_menu_params.h"
 #include "ui/base/window_open_disposition_utils.h"
+#include "ui/events/event_constants.h"
 
 namespace {
 // Measurement marks.
-constexpr char kChangeVisibleModeToLoadingStartMark[] =
-    "BrowserControls.ChangeVisibleModeToLoading.Start";
-constexpr char kChangeVisibleModeToNotLoadingStartMark[] =
-    "BrowserControls.ChangeVisibleModeToNotLoading.Start";
 constexpr char kInputMouseReleaseStartMark[] =
     "ReloadButton.Input.MouseRelease.Start";
 
@@ -67,32 +60,17 @@
 BrowserControlsService::BrowserControlsService(
     mojo::PendingReceiver<mojom::BrowserControlsService> service,
     std::unique_ptr<BrowserControlsAdapter> browser_adapter,
-    std::unique_ptr<NavigationControlsStateFetcher> state_fetcher,
     MetricsReporter* metrics_reporter,
-    BrowserControlsService::Delegate* delegate)
+    BrowserControlsServiceDelegate* delegate)
     : service_(this, std::move(service)),
       browser_adapter_(std::move(browser_adapter)),
-      state_fetcher_(std::move(state_fetcher)),
       metrics_reporter_(metrics_reporter),
       delegate_(delegate) {
   CHECK(browser_adapter_);
-  CHECK(metrics_reporter);
 }
 
 BrowserControlsService::~BrowserControlsService() = default;
 
-void BrowserControlsService::Bind(BindCallback callback) {
-  auto result = browser_controls_api::mojom::InitialState::New();
-  result->state = state_fetcher_->GetNavigationControlsState();
-
-  mojo::Remote<browser_controls_api::mojom::BrowserControlsObserver> observer;
-  result->update_stream = observer.BindNewPipeAndPassReceiver();
-
-  observers_.Add(std::move(observer));
-
-  std::move(callback).Run(std::move(result));
-}
-
 void BrowserControlsService::ReloadFromClick(
     bool bypass_cache,
     const std::vector<browser_controls_api::mojom::ClickDispositionFlag>&
@@ -138,41 +116,14 @@
   // TODO(crbug.com/448794588): Handle KeyPress events.
 }
 
-void BrowserControlsService::ShowContextMenu(
-    browser_controls_api::mojom::ContextMenuType menu_type,
-    const gfx::Point& viewport_coordinate_css_pixels,
-    ui::mojom::MenuSourceType source) {
-  if (delegate_) {
-    delegate_->HandleContextMenu(menu_type, viewport_coordinate_css_pixels,
-                                 source);
-  }
-}
-
-void BrowserControlsService::OnPageInitialized() {
-  if (delegate_) {
-    delegate_->OnPageInitialized();
-  }
-}
-
-void BrowserControlsService::OnNavigationControlsStateChanged(
-    const browser_controls_api::mojom::NavigationControlsStatePtr& state) {
-  auto* mark = state->reload_control_state->is_navigation_loading
-                   ? kChangeVisibleModeToLoadingStartMark
-                   : kChangeVisibleModeToNotLoadingStartMark;
-  metrics_reporter_->Mark(mark);
-
-  for (auto& observer : observers_) {
-    observer->OnNavigationControlsStateChanged(state.Clone());
-  }
-}
-
 void BrowserControlsService::SplitActiveTab() {
   // We only reach here if the frontend decided we need to CREATE a split.
   // We don't need to check IsActiveTabInSplit() or handle the menu here.
   browser_adapter_->CreateNewSplitTab();
 }
 
-void BrowserControlsService::SetDelegate(Delegate* delegate) {
+void BrowserControlsService::SetDelegate(
+    BrowserControlsServiceDelegate* delegate) {
   delegate_ = delegate;
 }
 
diff --git a/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.h b/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.h
index 3b95b98..380c1b14 100644
--- a/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.h
+++ b/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.h
@@ -9,17 +9,10 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/tabs/split_tab_menu_model.h"
 #include "chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter.h"
-#include "chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher.h"
 #include "components/browser_apis/browser_controls/browser_controls_api.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
-#include "mojo/public/cpp/bindings/remote_set.h"
-#include "ui/base/models/menu_model.h"
-#include "ui/base/mojom/menu_source_type.mojom-shared.h"
-#include "ui/views/controls/menu/menu_runner.h"
 
 class MetricsReporter;
 
@@ -28,47 +21,32 @@
 class BrowserControlsService
     : public browser_controls_api::mojom::BrowserControlsService {
  public:
-  class Delegate {
+  class BrowserControlsServiceDelegate {
    public:
-    virtual ~Delegate() = default;
-
-    virtual void HandleContextMenu(
-        browser_controls_api::mojom::ContextMenuType menu_type,
-        gfx::Point viewport_coordinate_css_pixels,
-        ui::mojom::MenuSourceType source) = 0;
-    virtual void OnPageInitialized() = 0;
+    virtual ~BrowserControlsServiceDelegate() = default;
     virtual void PermitLaunchUrl() = 0;
   };
 
   BrowserControlsService(
       mojo::PendingReceiver<mojom::BrowserControlsService> service,
       std::unique_ptr<BrowserControlsAdapter> browser_adapter,
-      std::unique_ptr<NavigationControlsStateFetcher> state_fetcher,
       MetricsReporter* metrics_reporter,
-      Delegate* delegate);
+      BrowserControlsServiceDelegate* delegate);
 
   BrowserControlsService(const BrowserControlsService&) = delete;
   BrowserControlsService& operator=(const BrowserControlsService&) = delete;
 
   ~BrowserControlsService() override;
 
-  void SetDelegate(Delegate* delegate);
+  void SetDelegate(BrowserControlsServiceDelegate* delegate);
 
   // browser_controls_api::mojom::BrowserControlsService:
-  void Bind(BindCallback callback) override;
   void ReloadFromClick(
       bool bypass_cache,
       const std::vector<mojom::ClickDispositionFlag>& click_flags) override;
   void StopLoad() override;
-  void ShowContextMenu(mojom::ContextMenuType menu_type,
-                       const gfx::Point& viewport_coordinate_css_pixels,
-                       ui::mojom::MenuSourceType source) override;
-  void OnPageInitialized() override;
   void SplitActiveTab() override;
 
-  void OnNavigationControlsStateChanged(
-      const browser_controls_api::mojom::NavigationControlsStatePtr& state);
-
  private:
   // Callback for `MetricsReporter::Measure()`. Records the resulting
   // base::TimeDelta to the given UMA histogram and clears the start mark.
@@ -77,15 +55,11 @@
                                    base::TimeDelta duration);
 
   mojo::Receiver<browser_controls_api::mojom::BrowserControlsService> service_;
-  mojo::RemoteSet<browser_controls_api::mojom::BrowserControlsObserver>
-      observers_;
-
   std::unique_ptr<BrowserControlsAdapter> browser_adapter_;
-  std::unique_ptr<NavigationControlsStateFetcher> state_fetcher_;
 
   // Not owned.
   raw_ptr<MetricsReporter> metrics_reporter_;
-  raw_ptr<Delegate> delegate_;
+  raw_ptr<BrowserControlsServiceDelegate> delegate_;
 
   // Must be the last member.
   base::WeakPtrFactory<BrowserControlsService> weak_ptr_factory_{this};
diff --git a/chrome/browser/ui/webui/webui_toolbar/browser_controls_service_unittest.cc b/chrome/browser/ui/webui/webui_toolbar/browser_controls_service_unittest.cc
index 64441451..c2fb4e4a 100644
--- a/chrome/browser/ui/webui/webui_toolbar/browser_controls_service_unittest.cc
+++ b/chrome/browser/ui/webui/webui_toolbar/browser_controls_service_unittest.cc
@@ -7,8 +7,7 @@
 #include <memory>
 #include <utility>
 
-#include "base/metrics/histogram_samples.h"
-#include "base/metrics/statistics_recorder.h"
+#include "base/memory/raw_ptr.h"
 #include "base/test/bind.h"
 #include "base/test/gmock_callback_support.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -19,20 +18,14 @@
 #include "chrome/browser/ui/browser_window/test/mock_browser_window_interface.h"
 #include "chrome/browser/ui/webui/metrics_reporter/metrics_reporter_service.h"
 #include "chrome/browser/ui/webui/metrics_reporter/mock_metrics_reporter.h"
-#include "chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher_impl.h"
 #include "chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.h"
 #include "chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.h"
-#include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/browser_apis/browser_controls/browser_controls_api.mojom.h"
-#include "components/prefs/pref_service.h"
-#include "content/public/browser/context_menu_params.h"
-#include "content/public/browser/web_contents_delegate.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_renderer_host.h"
 #include "content/public/test/web_contents_tester.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/window_open_disposition.h"
@@ -43,15 +36,10 @@
 
 using ::testing::_;
 using ::testing::Eq;
-using ::testing::InvokeArgument;
 using ::testing::Return;
 using testing::ToyBrowser;
 
 // Measurement marks.
-constexpr char kChangeVisibleModeToLoadingStartMark[] =
-    "BrowserControls.ChangeVisibleModeToLoading.Start";
-constexpr char kChangeVisibleModeToNotLoadingStartMark[] =
-    "BrowserControls.ChangeVisibleModeToNotLoading.Start";
 constexpr char kInputMouseReleaseStartMark[] =
     "ReloadButton.Input.MouseRelease.Start";
 
@@ -61,55 +49,14 @@
 constexpr char kInputToStopMouseReleaseHistogram[] =
     "InitialWebUI.ReloadButton.InputToStop.MouseRelease";
 
-class MockWebWebUIToolbarDelegate : public BrowserControlsService::Delegate {
+class MockBrowserControlsServiceDelegate
+    : public BrowserControlsService::BrowserControlsServiceDelegate {
  public:
-  MockWebWebUIToolbarDelegate() = default;
+  MockBrowserControlsServiceDelegate() = default;
 
-  MOCK_METHOD(void,
-              HandleContextMenu,
-              (mojom::ContextMenuType, gfx::Point, ui::mojom::MenuSourceType),
-              (override));
-  MOCK_METHOD(void, OnPageInitialized, (), (override));
   MOCK_METHOD(void, PermitLaunchUrl, (), (override));
 };
 
-class Observer : public browser_controls_api::mojom::BrowserControlsObserver {
- public:
-  explicit Observer(BrowserControlsService* service) {
-    base::RunLoop run_loop;
-    service->Bind(base::BindLambdaForTesting(
-        [&](base::expected<browser_controls_api::mojom::InitialStatePtr,
-                           mojo_base::mojom::ErrorPtr> result) {
-          ASSERT_TRUE(result.has_value());
-          state = std::move(result.value()->state);
-          receiver_.Bind(std::move(result.value()->update_stream));
-          run_loop.Quit();
-        }));
-    run_loop.Run();
-
-    FlushForTesting();
-  }
-  ~Observer() override = default;
-
-  Observer(const Observer&) = delete;
-  Observer& operator=(const Observer&) = delete;
-
-  void OnNavigationControlsStateChanged(
-      mojom::NavigationControlsStatePtr changed) override {
-    state = std::move(changed);
-  }
-
-  void FlushForTesting() { receiver_.FlushForTesting(); }
-
-  // Easily accessible for testing. Start with nullopt to easily differentiate
-  // between uninitialized and unset.
-  mojom::NavigationControlsStatePtr state;
-
- private:
-  mojo::Receiver<browser_controls_api::mojom::BrowserControlsObserver>
-      receiver_{this};
-};
-
 // This is really an integration test. We provide a faked environment so that we
 // can have an easily predictable sealed environment to exercise the
 // interactions between our service and dependencies. To validate our
@@ -117,15 +64,9 @@
 class BrowserControlsServiceTest : public ::testing::Test {
  public:
   void SetUp() override {
-    auto fetcher = std::make_unique<NavigationControlsStateFetcherImpl>(
-        base::BindLambdaForTesting(
-            [&] { return navigation_controls_state().Clone(); }));
     service_ = std::make_unique<BrowserControlsService>(
         mojo::PendingReceiver<mojom::BrowserControlsService>(),
-        toy_browser_.GetAdapter(), std::move(fetcher), &metrics_reporter_,
-        &delegate_);
-    observer_ = std::make_unique<Observer>(service_.get());
-    observer_->FlushForTesting();
+        toy_browser_.GetAdapter(), &metrics_reporter_, &delegate_);
   }
 
   void TearDown() override { service_.reset(); }
@@ -143,32 +84,18 @@
     return metrics_reporter_;
   }
 
-  mojom::NavigationControlsStatePtr& navigation_controls_state() {
-    return navigation_controls_state_;
-  }
-  // Updates the service with the current navigation control state.
-  void PushNavigationControlsStateUpdate() {
-    service_->OnNavigationControlsStateChanged(
-        navigation_controls_state_.Clone());
-    observer()->FlushForTesting();
-  }
-
   ToyBrowser& toy_browser() { return toy_browser_; }
   BrowserControlsService& service() { return *service_; }
-  Observer* observer() { return observer_.get(); }
   base::HistogramTester& histogram_tester() { return histogram_tester_; }
-  MockWebWebUIToolbarDelegate& delegate() { return delegate_; }
+  MockBrowserControlsServiceDelegate& delegate() { return delegate_; }
 
  private:
   content::BrowserTaskEnvironment task_environment_;
   testing::ToyBrowser toy_browser_;
   ::testing::NiceMock<MockMetricsReporter> metrics_reporter_;
   std::unique_ptr<BrowserControlsService> service_;
-  std::unique_ptr<Observer> observer_;
   base::HistogramTester histogram_tester_;
-  MockWebWebUIToolbarDelegate delegate_;
-  mojom::NavigationControlsStatePtr navigation_controls_state_ =
-      CreateValidNavigationControlsState();
+  MockBrowserControlsServiceDelegate delegate_;
 };
 
 // Test suite for Reload-related tests.
@@ -234,112 +161,6 @@
   histogram_tester().ExpectUniqueTimeSample(kInputToStopMouseReleaseHistogram,
                                             duration, 1);
 }
-
-// Tests that calling ShowContextMenu() opens the context menu.
-TEST_F(BrowserControlsServiceTest, TestShowContextMenu) {
-  EXPECT_CALL(delegate(),
-              HandleContextMenu(::testing::_, ::testing::_, ::testing::_));
-
-  service().ShowContextMenu(mojom::ContextMenuType::kReload, gfx::Point(1, 2),
-                            ui::mojom::MenuSourceType::kMouse);
-}
-
-// Tests that calling OnNavigationControlsStateChanged() calls the page with the
-// correct state and records metrics when loading.
-TEST_F(BrowserControlsServiceTest, TestOnNavigationStatusChangedLoading) {
-  EXPECT_CALL(mock_metrics_reporter(),
-              Mark(kChangeVisibleModeToLoadingStartMark))
-      .Times(1);
-  ASSERT_FALSE(observer()->state->reload_control_state->is_navigation_loading);
-
-  navigation_controls_state()->reload_control_state->is_navigation_loading =
-      true;
-  PushNavigationControlsStateUpdate();
-
-  ASSERT_TRUE(observer()->state->reload_control_state->is_navigation_loading);
-}
-
-// Tests that calling OnNavigationControlsStateChanged() calls the page with the
-// correct state and records metrics when not loading.
-TEST_F(BrowserControlsServiceTest, TestOnNavigationStatusChangedNotLoading) {
-  EXPECT_CALL(mock_metrics_reporter(),
-              Mark(kChangeVisibleModeToNotLoadingStartMark))
-      .Times(1);
-
-  navigation_controls_state()->reload_control_state->is_navigation_loading =
-      false;
-  PushNavigationControlsStateUpdate();
-
-  ASSERT_FALSE(observer()->state->reload_control_state->is_navigation_loading);
-}
-
-// Tests that calling OnNavigationControlsStateChanged() calls the page with the
-// correct state.
-TEST_F(BrowserControlsServiceTest, TestOnDevToolsStatusChangedToConnected) {
-  ASSERT_FALSE(observer()->state->reload_control_state->is_devtools_connected);
-
-  navigation_controls_state()->reload_control_state->is_devtools_connected =
-      true;
-  PushNavigationControlsStateUpdate();
-
-  ASSERT_TRUE(observer()->state->reload_control_state->is_devtools_connected);
-}
-
-// Tests that multiple observers receive updates.
-TEST_F(BrowserControlsServiceTest, MultipleObserversReceiveUpdates) {
-  Observer observer2(&service());
-
-  EXPECT_CALL(mock_metrics_reporter(),
-              Mark(kChangeVisibleModeToLoadingStartMark))
-      .Times(1);
-
-  ASSERT_FALSE(observer()->state->reload_control_state->is_navigation_loading);
-  ASSERT_FALSE(observer2.state->reload_control_state->is_navigation_loading);
-
-  navigation_controls_state()->reload_control_state->is_navigation_loading =
-      true;
-  PushNavigationControlsStateUpdate();
-  observer2.FlushForTesting();
-
-  ASSERT_TRUE(observer()->state->reload_control_state->is_navigation_loading);
-  ASSERT_TRUE(observer2.state->reload_control_state->is_navigation_loading);
-}
-
-// Test suite for SplitTabs-related tests.
-using BrowserControlsServiceSplitTabsTest = BrowserControlsServiceTest;
-
-// Tests that OnNavigationControlsStateChanged calls the page with the correct
-// state.
-TEST_F(BrowserControlsServiceSplitTabsTest, TestOnTabSplitStatusChanged) {
-  navigation_controls_state()->split_tabs_control_state->is_current_tab_split =
-      true;
-  navigation_controls_state()->split_tabs_control_state->location =
-      browser_controls_api::mojom::SplitTabActiveLocation::kStart;
-  PushNavigationControlsStateUpdate();
-
-  ASSERT_TRUE(
-      observer()->state->split_tabs_control_state->is_current_tab_split);
-  ASSERT_EQ(mojom::SplitTabActiveLocation::kStart,
-            observer()->state->split_tabs_control_state->location);
-}
-
-// Tests that OnNavigationControlsStateChanged calls the page with the correct
-// state.
-TEST_F(BrowserControlsServiceSplitTabsTest,
-       TestOnSplitTabsButtonPinStateChanged) {
-  navigation_controls_state()->split_tabs_control_state->is_pinned = true;
-  PushNavigationControlsStateUpdate();
-
-  ASSERT_TRUE(observer()->state->split_tabs_control_state->is_pinned);
-}
-
-// Tests that OnPageInitialized calls the delegate.
-TEST_F(BrowserControlsServiceSplitTabsTest, TestOnPageInitializedDelegates) {
-  // Delegate OnPageInitialized should be called.
-  EXPECT_CALL(delegate(), OnPageInitialized()).Times(1);
-
-  service().OnPageInitialized();
-}
-
 }  // namespace
+
 }  // namespace browser_controls_api
diff --git a/chrome/browser/ui/webui/webui_toolbar/testing/BUILD.gn b/chrome/browser/ui/webui/webui_toolbar/testing/BUILD.gn
index 9dccb76e..3c05fa8 100644
--- a/chrome/browser/ui/webui/webui_toolbar/testing/BUILD.gn
+++ b/chrome/browser/ui/webui/webui_toolbar/testing/BUILD.gn
@@ -14,6 +14,7 @@
     "//chrome/browser/ui/webui/webui_toolbar/adapters",
     "//chrome/browser/ui/webui/webui_toolbar/utils",
     "//components/browser_apis/browser_controls:mojom_data_model",
+    "//components/browser_apis/ui_controllers/toolbar:mojom_data_model",
     "//ui/base:types",
   ]
 }
diff --git a/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.cc b/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.cc
index e52112b..d590672 100644
--- a/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.cc
+++ b/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.cc
@@ -39,7 +39,7 @@
     return status;
   }
 
-  bool IsButtonPinned(mojom::ToolbarButtonType type) override {
+  bool IsButtonPinned(toolbar_ui_api::mojom::ToolbarButtonType type) override {
     return toy_browser_->IsButtonPinned(type);
   }
 
@@ -51,18 +51,19 @@
   return std::make_unique<ToyBrowserControlsAdapter>(this);
 }
 
-void ToyBrowser::PinButton(mojom::ToolbarButtonType type) {
+void ToyBrowser::PinButton(toolbar_ui_api::mojom::ToolbarButtonType type) {
   pinned_buttons_.insert(type);
 }
 
-void ToyBrowser::UnpinButton(mojom::ToolbarButtonType type) {
+void ToyBrowser::UnpinButton(toolbar_ui_api::mojom::ToolbarButtonType type) {
   auto found = pinned_buttons_.find(type);
   if (found != pinned_buttons_.end()) {
     pinned_buttons_.erase(found);
   }
 }
 
-bool ToyBrowser::IsButtonPinned(mojom::ToolbarButtonType type) const {
+bool ToyBrowser::IsButtonPinned(
+    toolbar_ui_api::mojom::ToolbarButtonType type) const {
   return pinned_buttons_.contains(type);
 }
 
diff --git a/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.h b/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.h
index 9e2b083..7b7c5eb 100644
--- a/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.h
+++ b/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.h
@@ -11,6 +11,7 @@
 
 #include "chrome/app/chrome_command_ids.h"
 #include "components/browser_apis/browser_controls/browser_controls_api_data_model.mojom.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom.h"
 #include "ui/base/window_open_disposition.h"
 
 namespace browser_controls_api {
@@ -42,16 +43,16 @@
   }
 
   // Noop if the pin state doesn't change.
-  void PinButton(mojom::ToolbarButtonType type);
-  void UnpinButton(mojom::ToolbarButtonType type);
-  bool IsButtonPinned(mojom::ToolbarButtonType type) const;
+  void PinButton(toolbar_ui_api::mojom::ToolbarButtonType type);
+  void UnpinButton(toolbar_ui_api::mojom::ToolbarButtonType type);
+  bool IsButtonPinned(toolbar_ui_api::mojom::ToolbarButtonType type) const;
 
   bool is_split_tab() const { return is_split_tab_; }
 
  private:
   friend class ToyBrowserControlsAdapter;
   std::vector<ToyBrowserCommand> received_commands_;
-  std::set<mojom::ToolbarButtonType> pinned_buttons_;
+  std::set<toolbar_ui_api::mojom::ToolbarButtonType> pinned_buttons_;
   // True when split tab is created. This state currently sticks, with no way
   // to unset it.
   bool is_split_tab_ = false;
diff --git a/chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service.cc b/chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service.cc
new file mode 100644
index 0000000..9a575b2
--- /dev/null
+++ b/chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service.cc
@@ -0,0 +1,84 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service.h"
+
+#include <utility>
+
+#include "base/check.h"
+#include "base/functional/bind.h"
+#include "base/types/expected.h"
+#include "chrome/browser/ui/webui/metrics_reporter/metrics_reporter.h"
+#include "chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom.h"
+
+namespace {
+// Measurement marks.
+constexpr char kChangeVisibleModeToLoadingStartMark[] =
+    "ToolbarUI.ChangeVisibleModeToLoading.Start";
+constexpr char kChangeVisibleModeToNotLoadingStartMark[] =
+    "ToolbarUI.ChangeVisibleModeToNotLoading.Start";
+}  // namespace
+
+namespace toolbar_ui_api {
+
+ToolbarUIService::ToolbarUIService(
+    mojo::PendingReceiver<toolbar_ui_api::mojom::ToolbarUIService> service,
+    std::unique_ptr<NavigationControlsStateFetcher> state_fetcher,
+    MetricsReporter* metrics_reporter,
+    ToolbarUIServiceDelegate* delegate)
+    : service_(this, std::move(service)),
+      state_fetcher_(std::move(state_fetcher)),
+      metrics_reporter_(metrics_reporter),
+      delegate_(delegate) {
+  CHECK(state_fetcher_);
+}
+
+ToolbarUIService::~ToolbarUIService() = default;
+
+void ToolbarUIService::SetDelegate(ToolbarUIServiceDelegate* delegate) {
+  delegate_ = delegate;
+}
+
+void ToolbarUIService::OnNavigationControlsStateChanged(
+    const mojom::NavigationControlsStatePtr& state) {
+  auto* mark = state->reload_control_state->is_navigation_loading
+                   ? kChangeVisibleModeToLoadingStartMark
+                   : kChangeVisibleModeToNotLoadingStartMark;
+  metrics_reporter_->Mark(mark);
+
+  for (auto& observer : observers_) {
+    observer->OnNavigationControlsStateChanged(state.Clone());
+  }
+}
+
+void ToolbarUIService::Bind(BindCallback callback) {
+  auto result = toolbar_ui_api::mojom::InitialState::New();
+  result->state = state_fetcher_->GetNavigationControlsState();
+
+  mojo::Remote<toolbar_ui_api::mojom::ToolbarUIObserver> observer;
+  result->update_stream = observer.BindNewPipeAndPassReceiver();
+
+  observers_.Add(std::move(observer));
+
+  std::move(callback).Run(std::move(result));
+}
+
+void ToolbarUIService::ShowContextMenu(
+    toolbar_ui_api::mojom::ContextMenuType menu_type,
+    const gfx::Point& viewport_coordinate_css_pixels,
+    ui::mojom::MenuSourceType source) {
+  if (delegate_) {
+    delegate_->HandleContextMenu(menu_type, viewport_coordinate_css_pixels,
+                                 source);
+  }
+}
+
+void ToolbarUIService::OnPageInitialized() {
+  if (delegate_) {
+    delegate_->OnPageInitialized();
+  }
+}
+
+}  // namespace toolbar_ui_api
diff --git a/chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service.h b/chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service.h
new file mode 100644
index 0000000..53402990
--- /dev/null
+++ b/chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service.h
@@ -0,0 +1,74 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_WEBUI_TOOLBAR_TOOLBAR_UI_SERVICE_H_
+#define CHROME_BROWSER_UI_WEBUI_WEBUI_TOOLBAR_TOOLBAR_UI_SERVICE_H_
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+#include "ui/base/mojom/menu_source_type.mojom-shared.h"
+#include "ui/base/pointer/touch_ui_controller.h"
+
+class MetricsReporter;
+
+namespace toolbar_ui_api {
+
+class ToolbarUIService : public toolbar_ui_api::mojom::ToolbarUIService {
+ public:
+  class ToolbarUIServiceDelegate {
+   public:
+    virtual ~ToolbarUIServiceDelegate() = default;
+    virtual void HandleContextMenu(
+        toolbar_ui_api::mojom::ContextMenuType menu_type,
+        gfx::Point viewport_coordinate_css_pixels,
+        ui::mojom::MenuSourceType source) = 0;
+    virtual void OnPageInitialized() = 0;
+  };
+
+  ToolbarUIService(
+      mojo::PendingReceiver<toolbar_ui_api::mojom::ToolbarUIService> service,
+      std::unique_ptr<NavigationControlsStateFetcher> state_fetcher,
+      MetricsReporter* metrics_reporter,
+      ToolbarUIServiceDelegate* delegate);
+
+  ToolbarUIService(const ToolbarUIService&) = delete;
+  ToolbarUIService& operator=(const ToolbarUIService&) = delete;
+
+  ~ToolbarUIService() override;
+
+  void SetDelegate(ToolbarUIServiceDelegate* delegate);
+
+  void OnNavigationControlsStateChanged(
+      const mojom::NavigationControlsStatePtr& state);
+
+  // toolbar_ui_api::mojom::ToolbarUIService:
+  void Bind(BindCallback callback) override;
+  void ShowContextMenu(toolbar_ui_api::mojom::ContextMenuType menu_type,
+                       const gfx::Point& viewport_coordinate_css_pixels,
+                       ui::mojom::MenuSourceType source) override;
+  void OnPageInitialized() override;
+
+ private:
+  mojo::Receiver<toolbar_ui_api::mojom::ToolbarUIService> service_;
+  mojo::RemoteSet<toolbar_ui_api::mojom::ToolbarUIObserver> observers_;
+
+  std::unique_ptr<NavigationControlsStateFetcher> state_fetcher_;
+
+  // Not owned.
+  raw_ptr<MetricsReporter> metrics_reporter_;
+  raw_ptr<ToolbarUIServiceDelegate> delegate_;
+
+  // Must be the last member.
+  base::WeakPtrFactory<ToolbarUIService> weak_ptr_factory_{this};
+};
+
+}  // namespace toolbar_ui_api
+
+#endif  // CHROME_BROWSER_UI_WEBUI_WEBUI_TOOLBAR_TOOLBAR_UI_SERVICE_H_
diff --git a/chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service_unittest.cc b/chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service_unittest.cc
new file mode 100644
index 0000000..fe2b02e
--- /dev/null
+++ b/chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service_unittest.cc
@@ -0,0 +1,247 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/test/bind.h"
+#include "base/test/gmock_callback_support.h"
+#include "base/types/expected.h"
+#include "chrome/browser/ui/browser_window/test/mock_browser_window_interface.h"
+#include "chrome/browser/ui/webui/metrics_reporter/metrics_reporter_service.h"
+#include "chrome/browser/ui/webui/metrics_reporter/mock_metrics_reporter.h"
+#include "chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher_impl.h"
+#include "chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace toolbar_ui_api {
+
+namespace {
+
+using ::testing::_;
+using ::testing::Return;
+
+// Measurement marks.
+constexpr char kChangeVisibleModeToLoadingStartMark[] =
+    "ToolbarUI.ChangeVisibleModeToLoading.Start";
+constexpr char kChangeVisibleModeToNotLoadingStartMark[] =
+    "ToolbarUI.ChangeVisibleModeToNotLoading.Start";
+
+class MockToolbarUIServiceDelegate
+    : public ToolbarUIService::ToolbarUIServiceDelegate {
+ public:
+  MOCK_METHOD(void,
+              HandleContextMenu,
+              (mojom::ContextMenuType type,
+               gfx::Point location,
+               ui::mojom::MenuSourceType source),
+              (override));
+  MOCK_METHOD(void, OnPageInitialized, (), (override));
+};
+
+class Observer : public mojom::ToolbarUIObserver {
+ public:
+  explicit Observer(ToolbarUIService* service) {
+    base::RunLoop run_loop;
+    service->Bind(base::BindLambdaForTesting(
+        [&](base::expected<mojom::InitialStatePtr, mojo_base::mojom::ErrorPtr>
+                result) {
+          ASSERT_TRUE(result.has_value());
+          state = std::move(result.value()->state);
+          receiver_.Bind(std::move(result.value()->update_stream));
+          run_loop.Quit();
+        }));
+    run_loop.Run();
+
+    FlushForTesting();
+  }
+
+  ~Observer() override = default;
+
+  Observer(const Observer&) = delete;
+  Observer& operator=(const Observer&) = delete;
+
+  void OnNavigationControlsStateChanged(
+      mojom::NavigationControlsStatePtr changed) override {
+    state = std::move(changed);
+  }
+
+  void FlushForTesting() { receiver_.FlushForTesting(); }
+
+  // Easily accessible for testing. Start with nullopt to easily differentiate
+  // between uninitialized and unset.
+  mojom::NavigationControlsStatePtr state;
+
+ private:
+  mojo::Receiver<mojom::ToolbarUIObserver> receiver_{this};
+};
+
+// This is really an integration test. We provide a faked environment so that we
+// can have an easily predictable sealed environment to exercise the
+// interactions between our service and dependencies. To validate our
+// integration with "real" browser services, we should utilize browser tests.
+class ToolbarUIServiceTest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    auto fetcher = std::make_unique<NavigationControlsStateFetcherImpl>(
+        base::BindLambdaForTesting(
+            [&] { return navigation_controls_state().Clone(); }));
+    service_ = std::make_unique<ToolbarUIService>(
+        mojo::PendingReceiver<mojom::ToolbarUIService>(), std::move(fetcher),
+        &metrics_reporter_, &delegate_);
+    observer_ = std::make_unique<Observer>(service_.get());
+    observer_->FlushForTesting();
+  }
+
+  void TearDown() override { service_.reset(); }
+
+ protected:
+  ::testing::NiceMock<MockMetricsReporter>& mock_metrics_reporter() {
+    return metrics_reporter_;
+  }
+
+  mojom::NavigationControlsStatePtr& navigation_controls_state() {
+    return navigation_controls_state_;
+  }
+
+  // Updates the service with the current navigation control state.
+  void PushNavigationControlsStateUpdate() {
+    service_->OnNavigationControlsStateChanged(
+        navigation_controls_state_.Clone());
+    observer()->FlushForTesting();
+  }
+
+  ToolbarUIService& service() { return *service_; }
+  Observer* observer() { return observer_.get(); }
+  MockToolbarUIServiceDelegate& delegate() { return delegate_; }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  ::testing::NiceMock<MockMetricsReporter> metrics_reporter_;
+  std::unique_ptr<ToolbarUIService> service_;
+  std::unique_ptr<Observer> observer_;
+  MockToolbarUIServiceDelegate delegate_;
+  mojom::NavigationControlsStatePtr navigation_controls_state_ =
+      CreateValidNavigationControlsState();
+};
+
+// Tests that calling ShowContextMenu() opens the context menu.
+TEST_F(ToolbarUIServiceTest, TestShowContextMenu) {
+  EXPECT_CALL(delegate(),
+              HandleContextMenu(::testing::_, ::testing::_, ::testing::_));
+
+  service().ShowContextMenu(mojom::ContextMenuType::kReload, gfx::Point(1, 2),
+                            ui::mojom::MenuSourceType::kMouse);
+}
+
+// Tests that calling OnNavigationControlsStateChanged() calls the page with the
+// correct state and records metrics when loading.
+TEST_F(ToolbarUIServiceTest, TestOnNavigationStatusChangedLoading) {
+  EXPECT_CALL(mock_metrics_reporter(),
+              Mark(kChangeVisibleModeToLoadingStartMark))
+      .Times(1);
+  ASSERT_FALSE(observer()->state->reload_control_state->is_navigation_loading);
+
+  navigation_controls_state()->reload_control_state->is_navigation_loading =
+      true;
+  PushNavigationControlsStateUpdate();
+
+  ASSERT_TRUE(observer()->state->reload_control_state->is_navigation_loading);
+}
+
+// Tests that calling OnNavigationControlsStateChanged() calls the page with the
+// correct state and records metrics when not loading.
+TEST_F(ToolbarUIServiceTest, TestOnNavigationStatusChangedNotLoading) {
+  EXPECT_CALL(mock_metrics_reporter(),
+              Mark(kChangeVisibleModeToNotLoadingStartMark))
+      .Times(1);
+
+  navigation_controls_state()->reload_control_state->is_navigation_loading =
+      false;
+  PushNavigationControlsStateUpdate();
+
+  ASSERT_FALSE(observer()->state->reload_control_state->is_navigation_loading);
+}
+
+// Tests that calling OnNavigationControlsStateChanged() calls the page with the
+// correct state.
+TEST_F(ToolbarUIServiceTest, TestOnDevToolsStatusChangedToConnected) {
+  ASSERT_FALSE(observer()->state->reload_control_state->is_devtools_connected);
+
+  navigation_controls_state()->reload_control_state->is_devtools_connected =
+      true;
+  PushNavigationControlsStateUpdate();
+
+  ASSERT_TRUE(observer()->state->reload_control_state->is_devtools_connected);
+}
+
+// Tests that multiple observers receive updates.
+TEST_F(ToolbarUIServiceTest, MultipleObserversReceiveUpdates) {
+  Observer observer2(&service());
+
+  EXPECT_CALL(mock_metrics_reporter(),
+              Mark(kChangeVisibleModeToLoadingStartMark))
+      .Times(1);
+
+  ASSERT_FALSE(observer()->state->reload_control_state->is_navigation_loading);
+  ASSERT_FALSE(observer2.state->reload_control_state->is_navigation_loading);
+
+  navigation_controls_state()->reload_control_state->is_navigation_loading =
+      true;
+  PushNavigationControlsStateUpdate();
+  observer2.FlushForTesting();
+
+  ASSERT_TRUE(observer()->state->reload_control_state->is_navigation_loading);
+  ASSERT_TRUE(observer2.state->reload_control_state->is_navigation_loading);
+}
+
+// Test suite for SplitTabs-related tests.
+using ToolbarUIServiceSplitTabsTest = ToolbarUIServiceTest;
+
+// Tests that OnNavigationControlsStateChanged calls the page with the correct
+// state.
+TEST_F(ToolbarUIServiceSplitTabsTest, TestOnTabSplitStatusChanged) {
+  navigation_controls_state()->split_tabs_control_state->is_current_tab_split =
+      true;
+  navigation_controls_state()->split_tabs_control_state->location =
+      toolbar_ui_api::mojom::SplitTabActiveLocation::kStart;
+  PushNavigationControlsStateUpdate();
+
+  ASSERT_TRUE(
+      observer()->state->split_tabs_control_state->is_current_tab_split);
+  ASSERT_EQ(mojom::SplitTabActiveLocation::kStart,
+            observer()->state->split_tabs_control_state->location);
+}
+
+// Tests that OnNavigationControlsStateChanged calls the page with the correct
+// state.
+TEST_F(ToolbarUIServiceSplitTabsTest, TestOnSplitTabsButtonPinStateChanged) {
+  navigation_controls_state()->split_tabs_control_state->is_pinned = true;
+  PushNavigationControlsStateUpdate();
+
+  ASSERT_TRUE(observer()->state->split_tabs_control_state->is_pinned);
+}
+
+// Tests that OnPageInitialized calls the delegate.
+TEST_F(ToolbarUIServiceSplitTabsTest, TestOnPageInitializedDelegates) {
+  // Delegate OnPageInitialized should be called.
+  EXPECT_CALL(delegate(), OnPageInitialized()).Times(1);
+
+  service().OnPageInitialized();
+}
+
+}  // namespace
+
+}  // namespace toolbar_ui_api
diff --git a/chrome/browser/ui/webui/webui_toolbar/utils/BUILD.gn b/chrome/browser/ui/webui/webui_toolbar/utils/BUILD.gn
index cfee35b..b12d3ccb 100644
--- a/chrome/browser/ui/webui/webui_toolbar/utils/BUILD.gn
+++ b/chrome/browser/ui/webui/webui_toolbar/utils/BUILD.gn
@@ -12,6 +12,7 @@
     "//chrome/browser/ui/tabs",
     "//chrome/browser/ui/tabs:tab_strip",
     "//components/browser_apis/browser_controls:mojom_data_model",
+    "//components/browser_apis/ui_controllers/toolbar:mojom_data_model",
     "//ui/webui",
   ]
 }
diff --git a/chrome/browser/ui/webui/webui_toolbar/utils/split_tabs_utils.cc b/chrome/browser/ui/webui/webui_toolbar/utils/split_tabs_utils.cc
index a79b8478..eef5b04e 100644
--- a/chrome/browser/ui/webui/webui_toolbar/utils/split_tabs_utils.cc
+++ b/chrome/browser/ui/webui/webui_toolbar/utils/split_tabs_utils.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -38,20 +39,17 @@
 
     switch (location) {
       case split_tabs::SplitTabActiveLocation::kStart:
-        status.location =
-            browser_controls_api::mojom::SplitTabActiveLocation::kStart;
+        status.location = toolbar_ui_api::mojom::SplitTabActiveLocation::kStart;
         break;
       case split_tabs::SplitTabActiveLocation::kEnd:
-        status.location =
-            browser_controls_api::mojom::SplitTabActiveLocation::kEnd;
+        status.location = toolbar_ui_api::mojom::SplitTabActiveLocation::kEnd;
         break;
       case split_tabs::SplitTabActiveLocation::kTop:
-        status.location =
-            browser_controls_api::mojom::SplitTabActiveLocation::kTop;
+        status.location = toolbar_ui_api::mojom::SplitTabActiveLocation::kTop;
         break;
       case split_tabs::SplitTabActiveLocation::kBottom:
         status.location =
-            browser_controls_api::mojom::SplitTabActiveLocation::kBottom;
+            toolbar_ui_api::mojom::SplitTabActiveLocation::kBottom;
         break;
     }
   }
@@ -60,9 +58,9 @@
 }
 
 bool IsButtonPinned(BrowserWindowInterface* browser_interface,
-                    browser_controls_api::mojom::ToolbarButtonType type) {
+                    toolbar_ui_api::mojom::ToolbarButtonType type) {
   switch (type) {
-    case browser_controls_api::mojom::ToolbarButtonType::kSplitTabs:
+    case toolbar_ui_api::mojom::ToolbarButtonType::kSplitTabs:
       return browser_interface->GetProfile()->GetPrefs()->GetBoolean(
           prefs::kPinSplitTabButton);
     default:
diff --git a/chrome/browser/ui/webui/webui_toolbar/utils/split_tabs_utils.h b/chrome/browser/ui/webui/webui_toolbar/utils/split_tabs_utils.h
index f90fd27..5cb77f42 100644
--- a/chrome/browser/ui/webui/webui_toolbar/utils/split_tabs_utils.h
+++ b/chrome/browser/ui/webui/webui_toolbar/utils/split_tabs_utils.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_WEBUI_WEBUI_TOOLBAR_UTILS_SPLIT_TABS_UTILS_H_
 
 #include "components/browser_apis/browser_controls/browser_controls_api_data_model.mojom.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom.h"
 
 class BrowserWindowInterface;
 
@@ -18,8 +19,8 @@
 // Represents the split state of the active tab.
 struct TabSplitStatus {
   bool is_split = false;
-  browser_controls_api::mojom::SplitTabActiveLocation location =
-      browser_controls_api::mojom::SplitTabActiveLocation::kStart;
+  toolbar_ui_api::mojom::SplitTabActiveLocation location =
+      toolbar_ui_api::mojom::SplitTabActiveLocation::kStart;
 
   bool operator==(const TabSplitStatus& other) const = default;
 };
@@ -29,7 +30,7 @@
 
 // Gets the pin state from user prefs.
 bool IsButtonPinned(BrowserWindowInterface* browser_interface,
-                    browser_controls_api::mojom::ToolbarButtonType type);
+                    toolbar_ui_api::mojom::ToolbarButtonType type);
 
 // Populates the WebUI data source with split tabs specific strings and initial
 // state.
diff --git a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.cc b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.cc
index 0fb5f6a0..bed7c48b 100644
--- a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.cc
+++ b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.cc
@@ -7,14 +7,13 @@
 MockReloadButtonPage::MockReloadButtonPage() = default;
 MockReloadButtonPage::~MockReloadButtonPage() = default;
 
-mojo::PendingRemote<browser_controls_api::mojom::BrowserControlsObserver>
+mojo::PendingRemote<toolbar_ui_api::mojom::ToolbarUIObserver>
 MockReloadButtonPage::BindAndGetRemote() {
   return receiver_.BindNewPipeAndPassRemote();
 }
 
 void MockReloadButtonPage::Bind(
-    mojo::PendingReceiver<browser_controls_api::mojom::BrowserControlsObserver>
-        receiver) {
+    mojo::PendingReceiver<toolbar_ui_api::mojom::ToolbarUIObserver> receiver) {
   receiver_.Bind(std::move(receiver));
 }
 
@@ -22,14 +21,19 @@
   receiver_.FlushForTesting();
 }
 
-MockWebWebUIToolbarDelegate::MockWebWebUIToolbarDelegate() = default;
-MockWebWebUIToolbarDelegate::~MockWebWebUIToolbarDelegate() = default;
+MockToolbarUIServiceDelegate::MockToolbarUIServiceDelegate() = default;
+MockToolbarUIServiceDelegate::~MockToolbarUIServiceDelegate() = default;
 
-browser_controls_api::mojom::NavigationControlsStatePtr
+MockBrowserControlsServiceDelegate::MockBrowserControlsServiceDelegate() =
+    default;
+MockBrowserControlsServiceDelegate::~MockBrowserControlsServiceDelegate() =
+    default;
+
+toolbar_ui_api::mojom::NavigationControlsStatePtr
 CreateValidNavigationControlsState() {
-  return browser_controls_api::mojom::NavigationControlsState::New(
-      browser_controls_api::mojom::ReloadControlState::New(),
-      browser_controls_api::mojom::SplitTabsControlState::New(),
+  return toolbar_ui_api::mojom::NavigationControlsState::New(
+      toolbar_ui_api::mojom::ReloadControlState::New(),
+      toolbar_ui_api::mojom::SplitTabsControlState::New(),
       /*layout_constants_version=*/0);
 }
 
diff --git a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.h b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.h
index c782a9cb..ead49d1 100644
--- a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.h
+++ b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.h
@@ -7,17 +7,18 @@
 
 #include "chrome/browser/command_updater.h"
 #include "chrome/browser/ui/webui/webui_toolbar/browser_controls_service.h"
+#include "chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service.h"
 #include "components/browser_apis/browser_controls/browser_controls_api.mojom.h"
-#include "components/browser_apis/browser_controls/browser_controls_api_data_model.mojom.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/base/window_open_disposition.h"
 
 // Mock implementation of the
-// browser_controls_api::mojom::BrowserControlsObserver interface.
-class MockReloadButtonPage
-    : public browser_controls_api::mojom::BrowserControlsObserver {
+// toolbar_ui_api::mojom::ToolbarUIObserver interface.
+class MockReloadButtonPage : public toolbar_ui_api::mojom::ToolbarUIObserver {
  public:
   MockReloadButtonPage();
   ~MockReloadButtonPage() override;
@@ -26,43 +27,53 @@
   MockReloadButtonPage& operator=(const MockReloadButtonPage&) = delete;
 
   // Returns a PendingRemote to this mock implementation.
-  mojo::PendingRemote<browser_controls_api::mojom::BrowserControlsObserver>
+  mojo::PendingRemote<toolbar_ui_api::mojom::ToolbarUIObserver>
   BindAndGetRemote();
 
-  void Bind(mojo::PendingReceiver<
-            browser_controls_api::mojom::BrowserControlsObserver> receiver);
+  void Bind(
+      mojo::PendingReceiver<toolbar_ui_api::mojom::ToolbarUIObserver> receiver);
 
   void FlushForTesting();
 
-  // browser_controls_api::mojom::BrowserControlsObserver:
+  // toolbar_ui_api::mojom::ToolbarUIObserver:
   MOCK_METHOD(void,
               OnNavigationControlsStateChanged,
-              (browser_controls_api::mojom::NavigationControlsStatePtr state),
+              (toolbar_ui_api::mojom::NavigationControlsStatePtr state),
               (override));
 
  private:
-  mojo::Receiver<browser_controls_api::mojom::BrowserControlsObserver>
-      receiver_{this};
+  mojo::Receiver<toolbar_ui_api::mojom::ToolbarUIObserver> receiver_{this};
 };
 
-class MockWebWebUIToolbarDelegate
-    : public browser_controls_api::BrowserControlsService::Delegate {
+class MockToolbarUIServiceDelegate
+    : public toolbar_ui_api::ToolbarUIService::ToolbarUIServiceDelegate {
  public:
-  MockWebWebUIToolbarDelegate();
-  ~MockWebWebUIToolbarDelegate() override;
+  MockToolbarUIServiceDelegate();
+  ~MockToolbarUIServiceDelegate() override;
 
+  // ToolbarUIService::ToolbarUIServiceDelegate:
   MOCK_METHOD(void,
               HandleContextMenu,
-              (browser_controls_api::mojom::ContextMenuType,
+              (toolbar_ui_api::mojom::ContextMenuType,
                gfx::Point,
                ui::mojom::MenuSourceType),
               (override));
   MOCK_METHOD(void, OnPageInitialized, (), (override));
+};
+
+class MockBrowserControlsServiceDelegate
+    : public browser_controls_api::BrowserControlsService::
+          BrowserControlsServiceDelegate {
+ public:
+  MockBrowserControlsServiceDelegate();
+  ~MockBrowserControlsServiceDelegate() override;
+
+  // BrowserControlsService::BrowserControlsServiceDelegate:
   MOCK_METHOD(void, PermitLaunchUrl, (), (override));
 };
 
 // Helper to create a valid NavigationControlsState with initialized fields.
-browser_controls_api::mojom::NavigationControlsStatePtr
+toolbar_ui_api::mojom::NavigationControlsStatePtr
 CreateValidNavigationControlsState();
 
 // Mock implementation of CommandUpdater for testing.
diff --git a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.cc b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.cc
index 9df6fde..b42a443 100644
--- a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.cc
+++ b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.cc
@@ -6,7 +6,9 @@
 
 #include <memory>
 #include <utility>
+#include <vector>
 
+#include "base/check.h"
 #include "base/strings/strcat.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_command_controller.h"
@@ -26,6 +28,7 @@
 #include "chrome/browser/ui/webui/webui_embedding_context.h"
 #include "chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.h"
 #include "chrome/browser/ui/webui/webui_toolbar/browser_controls_service.h"
+#include "chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service.h"
 #include "chrome/browser/ui/webui/webui_toolbar/utils/split_tabs_utils.h"
 #include "chrome/browser/ui/webui/webui_toolbar/webui_toolbar_layout_css_helper.h"
 #include "chrome/common/chrome_features.h"
@@ -34,6 +37,7 @@
 #include "chrome/grit/webui_toolbar_resources.h"
 #include "chrome/grit/webui_toolbar_resources_map.h"
 #include "components/browser_apis/browser_controls/browser_controls_api.mojom.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui.h"
@@ -125,9 +129,28 @@
           std::move(receiver),
           std::make_unique<browser_controls_api::BrowserControlsAdapterImpl>(
               webui::GetBrowserWindowInterface(web_contents), command_updater),
-          dependency_provider_->GetNavigationControlsStateFetcher(),
           metrics_service->metrics_reporter(),
-          dependency_provider_->GetDelegate());
+          dependency_provider_->GetBrowserControlsDelegate());
+}
+
+void WebUIToolbarUI::BindInterface(
+    mojo::PendingReceiver<toolbar_ui_api::mojom::ToolbarUIService> receiver) {
+  CHECK(dependency_provider_)
+      << "Dependency provider is not set, make sure to call Init() first";
+
+  auto* web_contents = web_ui()->GetWebContents();
+  MetricsReporterService* metrics_service =
+      MetricsReporterService::GetFromWebContents(web_contents);
+
+  // If this CHECK() starts hitting, it could be due to races with browser
+  // shutdown, similar to issues seen in the past (e.g., b/478033216#comment4).
+  CHECK(metrics_service) << "Metrics service missing from web contents";
+
+  toolbar_ui_service_ = std::make_unique<toolbar_ui_api::ToolbarUIService>(
+      std::move(receiver),
+      dependency_provider_->GetNavigationControlsStateFetcher(),
+      metrics_service->metrics_reporter(),
+      dependency_provider_->GetToolbarUIServiceDelegate());
 }
 
 void WebUIToolbarUI::BindInterface(
@@ -146,10 +169,9 @@
 }
 
 void WebUIToolbarUI::OnNavigationControlsStateChanged(
-    browser_controls_api::mojom::NavigationControlsStatePtr state) {
-  if (browser_controls_service_) {
-    browser_controls_service_->OnNavigationControlsStateChanged(
-        std::move(state));
+    toolbar_ui_api::mojom::NavigationControlsStatePtr state) {
+  if (toolbar_ui_service_) {
+    toolbar_ui_service_->OnNavigationControlsStateChanged(std::move(state));
   }
 }
 
@@ -158,12 +180,11 @@
       << "Out of order initialization, the browser control service has already "
          "been instantiated.";
 
-  dependency_provider_ = dependency_provider;
-}
+  CHECK(!toolbar_ui_service_)
+      << "Out of order initialization, the toolbar UI service has already "
+         "been instantiated.";
 
-browser_controls_api::BrowserControlsService*
-WebUIToolbarUI::browser_controls_service_for_testing() {
-  return browser_controls_service_.get();
+  dependency_provider_ = dependency_provider;
 }
 
 CommandUpdater* WebUIToolbarUI::GetCommandUpdater() const {
diff --git a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.h b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.h
index 62f005c..22f899b 100644
--- a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.h
+++ b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.h
@@ -6,13 +6,18 @@
 #define CHROME_BROWSER_UI_WEBUI_WEBUI_TOOLBAR_WEBUI_TOOLBAR_UI_H_
 
 #include <memory>
+#include <string_view>
+#include <vector>
 
+#include "base/gtest_prod_util.h"
+#include "base/memory/raw_ptr.h"
 #include "chrome/browser/ui/webui/top_chrome/top_chrome_web_ui_controller.h"
 #include "chrome/browser/ui/webui/top_chrome/top_chrome_webui_config.h"
-#include "chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher.h"
 #include "chrome/browser/ui/webui/webui_toolbar/browser_controls_service.h"
+#include "chrome/browser/ui/webui/webui_toolbar/toolbar_ui_service.h"
 #include "components/browser_apis/browser_controls/browser_controls_api.mojom-forward.h"
 #include "components/browser_apis/browser_controls/browser_controls_api_data_model.mojom.h"
+#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom.h"
 #include "content/public/browser/webui_config.h"
 #include "content/public/common/url_constants.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -28,7 +33,7 @@
 namespace gfx {
 class FontList;
 }  // namespace gfx
-   //
+
 class CommandUpdater;
 
 // The webui controller for the webui toolbar. This class has a two part
@@ -39,10 +44,12 @@
   // Provides dependencies to this controller during init.
   class DependencyProvider {
    public:
-    virtual browser_controls_api::BrowserControlsService::Delegate*
-    GetDelegate() = 0;
-    virtual std::unique_ptr<
-        browser_controls_api::NavigationControlsStateFetcher>
+    virtual browser_controls_api::BrowserControlsService::
+        BrowserControlsServiceDelegate*
+        GetBrowserControlsDelegate() = 0;
+    virtual toolbar_ui_api::ToolbarUIService::ToolbarUIServiceDelegate*
+    GetToolbarUIServiceDelegate() = 0;
+    virtual std::unique_ptr<toolbar_ui_api::NavigationControlsStateFetcher>
     GetNavigationControlsStateFetcher() = 0;
   };
 
@@ -58,17 +65,18 @@
           receiver);
 
   void BindInterface(
+      mojo::PendingReceiver<toolbar_ui_api::mojom::ToolbarUIService> receiver);
+
+  void BindInterface(
       mojo::PendingReceiver<tracked_element::mojom::TrackedElementHandler>
           receiver);
 
   void OnNavigationControlsStateChanged(
-      browser_controls_api::mojom::NavigationControlsStatePtr state);
+      toolbar_ui_api::mojom::NavigationControlsStatePtr state);
 
   // The |depdency_provider| is expected to outlive this class.
   void Init(DependencyProvider* dependency_provider);
 
-  browser_controls_api::BrowserControlsService*
-  browser_controls_service_for_testing();
 
   // TopChromeWebUIController:
   // The controller uses `requesting_origin` to:
@@ -96,6 +104,15 @@
                                content::WebUIDataSource* source);
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(WebUIToolbarUITest,
+                           BindInterfaceBrowserControlsService);
+  FRIEND_TEST_ALL_PREFIXES(WebUIToolbarUITest, BindInterfaceToolbarUIService);
+  FRIEND_TEST_ALL_PREFIXES(WebUIToolbarUITest, CreateBrowserControlsService);
+  FRIEND_TEST_ALL_PREFIXES(WebUIToolbarUITest, CreateToolbarUIService);
+  FRIEND_TEST_ALL_PREFIXES(WebUIToolbarUITest,
+                           CreateBrowserControlsService_NullCommandUpdater);
+  FRIEND_TEST_ALL_PREFIXES(WebUIToolbarUITest,
+                           CreateToolbarUIService_NullCommandUpdater);
   CommandUpdater* GetCommandUpdater() const;
 
   // Returns the list of known element identifiers. These elements are HTML
@@ -105,6 +122,7 @@
 
   std::unique_ptr<browser_controls_api::BrowserControlsService>
       browser_controls_service_;
+  std::unique_ptr<toolbar_ui_api::ToolbarUIService> toolbar_ui_service_;
   std::unique_ptr<ui::TrackedElementHandler> tracked_element_handler_;
 
   raw_ptr<DependencyProvider> dependency_provider_;
diff --git a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui_browsertest.cc b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui_browsertest.cc
index 6c0714e..3ad0745 100644
--- a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui_browsertest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/run_loop.h"
 #include "chrome/browser/ui/webui/theme_colors_source_manager.h"
 #include "chrome/browser/ui/webui/theme_colors_source_manager_factory.h"
 #include "chrome/browser/ui/webui/webui_embedding_context.h"
@@ -31,22 +32,22 @@
 namespace {
 
 // Helper class to manage mojo remote to the WebUIToolbarUI.
-class MockBrowserControlsServiceConnection {
+class ToolbarUIServiceConnectionManager {
  public:
-  explicit MockBrowserControlsServiceConnection(WebUIToolbarUI* ui) {
+  explicit ToolbarUIServiceConnectionManager(WebUIToolbarUI* ui) {
     ui->BindInterface(service_remote_.BindNewPipeAndPassReceiver());
   }
 
   // Not movable or copyable.
-  MockBrowserControlsServiceConnection(
-      const MockBrowserControlsServiceConnection&) = delete;
-  MockBrowserControlsServiceConnection& operator=(
-      const MockBrowserControlsServiceConnection&) = delete;
+  ToolbarUIServiceConnectionManager(const ToolbarUIServiceConnectionManager&) =
+      delete;
+  ToolbarUIServiceConnectionManager& operator=(
+      const ToolbarUIServiceConnectionManager&) = delete;
 
   void RegisterObserver() {
     service_remote_->Bind(base::BindOnce(
-        [](MockBrowserControlsServiceConnection* self,
-           base::expected<browser_controls_api::mojom::InitialStatePtr,
+        [](ToolbarUIServiceConnectionManager* self,
+           base::expected<toolbar_ui_api::mojom::InitialStatePtr,
                           mojo_base::mojom::ErrorPtr> result) {
           ASSERT_TRUE(result.has_value());
           self->mock_observer_.Bind(std::move(result.value()->update_stream));
@@ -64,21 +65,52 @@
 
  private:
   testing::StrictMock<MockReloadButtonPage> mock_observer_;
+  mojo::Remote<toolbar_ui_api::mojom::ToolbarUIService> service_remote_;
+};
+
+// Helper class to manage mojo remote to the WebUIToolbarUI.
+class BrowserControlsServiceConnectionManager {
+ public:
+  explicit BrowserControlsServiceConnectionManager(WebUIToolbarUI* ui) {
+    ui->BindInterface(service_remote_.BindNewPipeAndPassReceiver());
+  }
+
+  // Not movable or copyable.
+  BrowserControlsServiceConnectionManager(
+      const BrowserControlsServiceConnectionManager&) = delete;
+  BrowserControlsServiceConnectionManager& operator=(
+      const BrowserControlsServiceConnectionManager&) = delete;
+
+  void FlushForTesting() { service_remote_.FlushForTesting(); }
+
+  bool is_bound() { return service_remote_.is_bound(); }
+  bool is_connected() { return service_remote_.is_connected(); }
+
+ private:
   mojo::Remote<browser_controls_api::mojom::BrowserControlsService>
       service_remote_;
 };
 
 class BrowserControlsDelegate
-    : public browser_controls_api::BrowserControlsService::Delegate {
+    : public browser_controls_api::BrowserControlsService::
+          BrowserControlsServiceDelegate {
  public:
   BrowserControlsDelegate() = default;
   ~BrowserControlsDelegate() override = default;
 
-  void HandleContextMenu(browser_controls_api::mojom::ContextMenuType menu_type,
+  void PermitLaunchUrl() override {}
+};
+
+class ToolbarUIDelegate
+    : public toolbar_ui_api::ToolbarUIService::ToolbarUIServiceDelegate {
+ public:
+  ToolbarUIDelegate() = default;
+  ~ToolbarUIDelegate() override = default;
+
+  void HandleContextMenu(toolbar_ui_api::mojom::ContextMenuType menu_type,
                          gfx::Point viewport_coordinate_css_pixels,
                          ui::mojom::MenuSourceType source) override {}
   void OnPageInitialized() override {}
-  void PermitLaunchUrl() override {}
 };
 
 // Test fixture for WebUIToolbarUI. These tests test the connectivity between
@@ -119,14 +151,17 @@
   }
 
   // WebUIToolbarUI::DependencyProvider:
-  browser_controls_api::BrowserControlsService::Delegate* GetDelegate()
-      override {
-    return &delegate_;
+  browser_controls_api::BrowserControlsService::BrowserControlsServiceDelegate*
+  GetBrowserControlsDelegate() override {
+    return &browser_controls_delegate_;
   }
-  std::unique_ptr<browser_controls_api::NavigationControlsStateFetcher>
+  toolbar_ui_api::ToolbarUIService::ToolbarUIServiceDelegate*
+  GetToolbarUIServiceDelegate() override {
+    return &toolbar_ui_delegate_;
+  }
+  std::unique_ptr<toolbar_ui_api::NavigationControlsStateFetcher>
   GetNavigationControlsStateFetcher() override {
-    return std::make_unique<
-        browser_controls_api::NavigationControlsStateFetcherImpl>(
+    return std::make_unique<toolbar_ui_api::NavigationControlsStateFetcherImpl>(
         base::BindRepeating(
             [&] { return CreateValidNavigationControlsState(); }));
   }
@@ -136,7 +171,8 @@
 
  private:
   base::test::ScopedFeatureList feature_list_;
-  BrowserControlsDelegate delegate_;
+  BrowserControlsDelegate browser_controls_delegate_;
+  ToolbarUIDelegate toolbar_ui_delegate_;
   std::unique_ptr<content::TestWebUI> web_ui_;
   std::unique_ptr<WebUIToolbarUI> ui_;
 };
@@ -144,20 +180,19 @@
 // Tests that OnNavigationControlsStateChanged calls the browser controls
 // observer with the correct parameters.
 IN_PROC_BROWSER_TEST_F(WebUIToolbarUIBrowserTest, SetReloadButtonState) {
-  MockBrowserControlsServiceConnection connection(ui());
+  ToolbarUIServiceConnectionManager connection(ui());
 
   auto state = CreateValidNavigationControlsState();
   state->reload_control_state->is_navigation_loading = true;
   connection.RegisterObserver();
 
-  EXPECT_CALL(connection.mock_observer(),
-              OnNavigationControlsStateChanged(testing::Pointee(testing::Field(
-                  &browser_controls_api::mojom::NavigationControlsState::
-                      reload_control_state,
-                  testing::Pointee(testing::Field(
-                      &browser_controls_api::mojom::ReloadControlState::
-                          is_navigation_loading,
-                      true))))))
+  EXPECT_CALL(
+      connection.mock_observer(),
+      OnNavigationControlsStateChanged(testing::Pointee(testing::Field(
+          &toolbar_ui_api::mojom::NavigationControlsState::reload_control_state,
+          testing::Pointee(testing::Field(
+              &toolbar_ui_api::mojom::ReloadControlState::is_navigation_loading,
+              true))))))
       .Times(1);
   ui()->OnNavigationControlsStateChanged(std::move(state));
   connection.mock_observer().FlushForTesting();
@@ -165,18 +200,35 @@
 
 // Tests that the BindInterface method for BrowserControlsService works
 // correctly.
-IN_PROC_BROWSER_TEST_F(WebUIToolbarUIBrowserTest, BindService) {
-  MockBrowserControlsServiceConnection connection(ui());
+IN_PROC_BROWSER_TEST_F(WebUIToolbarUIBrowserTest, BindBrowserControlsService) {
+  BrowserControlsServiceConnectionManager connection(ui());
+
+  EXPECT_TRUE(connection.is_bound());
+  EXPECT_TRUE(connection.is_connected());
+}
+
+// Tests that the BindInterface method for ToolbarUIService works
+// correctly.
+IN_PROC_BROWSER_TEST_F(WebUIToolbarUIBrowserTest, BindToolbarUIService) {
+  ToolbarUIServiceConnectionManager connection(ui());
 
   EXPECT_TRUE(connection.is_bound());
   EXPECT_TRUE(connection.is_connected());
 }
 
 // Tests that connecting to the service instantiates the BrowserControlsService.
-IN_PROC_BROWSER_TEST_F(WebUIToolbarUIBrowserTest, CreateService) {
-  MockBrowserControlsServiceConnection connection(ui());
+IN_PROC_BROWSER_TEST_F(WebUIToolbarUIBrowserTest,
+                       CreateBrowserControlsService) {
+  BrowserControlsServiceConnectionManager connection(ui());
 
-  EXPECT_THAT(ui()->browser_controls_service_for_testing(), testing::NotNull());
+  EXPECT_TRUE(connection.is_connected());
+}
+
+// Tests that connecting to the service instantiates the ToolbarUIService.
+IN_PROC_BROWSER_TEST_F(WebUIToolbarUIBrowserTest, CreateToolbarUIService) {
+  ToolbarUIServiceConnectionManager connection(ui());
+
+  EXPECT_TRUE(connection.is_connected());
 }
 
 // Tests that Service creation handles a null CommandUpdater gracefully.
@@ -192,7 +244,7 @@
 
   web_ui()->set_web_contents(dummy_content.get());
 
-  MockBrowserControlsServiceConnection connection(ui());
+  BrowserControlsServiceConnectionManager connection(ui());
   // This line is necessary, because there is a defect in mojo's is_connected()
   // which erroneously return true without this, even if the remote is not
   // actually connected.
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index e7ba1327..12bf721 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1772215198-55b1e814606ae26f17dd80d83724051e0fd912ff-c1d741b15fbbb33be4ab55ba150d481080e6c83b.profdata
+chrome-android32-main-1772253278-aa6de317dad5d5d4e7914bfa50fcfe200bb5aa81-3633b670e86af329be8ecfe3d73ba9f927f48bb3.profdata
diff --git a/chrome/build/android-desktop-x64.pgo.txt b/chrome/build/android-desktop-x64.pgo.txt
index 195300cf..aa05835 100644
--- a/chrome/build/android-desktop-x64.pgo.txt
+++ b/chrome/build/android-desktop-x64.pgo.txt
@@ -1 +1 @@
-chrome-android-desktop-x64-main-1772215198-03242c792579d32531d5810e0404f84e64aea45d-c1d741b15fbbb33be4ab55ba150d481080e6c83b.profdata
+chrome-android-desktop-x64-main-1772236775-7b6cfce113f300048aa89b8b79a6a9f09e515b2c-a0f393d920ee0ffde8f21326fd8e77fbfdd17956.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index ce0832b..57fa5d2 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1772215198-bdc4b2f2b7c274e320a7cf09bd275900cc667a9c-c1d741b15fbbb33be4ab55ba150d481080e6c83b.profdata
+chrome-linux-main-1772236775-97328bc5e6d19ce7c22898fbc27c22e23ee0a12b-a0f393d920ee0ffde8f21326fd8e77fbfdd17956.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 51576f4f..ffe2470 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1772229576-5f973b7e08b34e8902f3d9d5706aee7d97435c5a-b2513bbf80c7e1a417bfc15796db5bce80c98a37.profdata
+chrome-mac-arm-main-1772253278-9d63e83bbdb4157781b1affe4f08a318ab6a3ac8-3633b670e86af329be8ecfe3d73ba9f927f48bb3.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index f6e7e525..a341d00d 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1772193450-1dc8f8da3202b18663507f2c9cf5a32ee404348b-4df9eebf17daee51c0decb393c6aac2cc86bf7c9.profdata
+chrome-mac-main-1772253278-6d849fb6b91517f8151995fcdcec3b7f65c2c27a-3633b670e86af329be8ecfe3d73ba9f927f48bb3.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index b6e494b41..fada1b9 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1772215198-d27adaf22adcf9dc47b7b2ade2442bbce753b9ae-c1d741b15fbbb33be4ab55ba150d481080e6c83b.profdata
+chrome-win-arm64-main-1772253278-d9c5e62094cf84e551a3605bcb00c73b7a0f7723-3633b670e86af329be8ecfe3d73ba9f927f48bb3.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index a36a266..8ca1cf1 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1772215198-1ac559f8ef7c73841d5dbf73bf8821988c8fdd2f-c1d741b15fbbb33be4ab55ba150d481080e6c83b.profdata
+chrome-win32-main-1772247500-aff4241956ae836e92b1c6d60727d8cc6dfb8980-da952433df6bb86b50d03dd9d54ad399b1b90ffc.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 95684d7..9d0b1ed 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1772215198-3d16dd670474a2977dc3cf78b91b275bad70451a-c1d741b15fbbb33be4ab55ba150d481080e6c83b.profdata
+chrome-win64-main-1772247500-0b6f95fb586cd21e01acf13cd6d68e853d63cf35-da952433df6bb86b50d03dd9d54ad399b1b90ffc.profdata
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
index 7dc23a3..b43fe9e 100644
--- a/chrome/common/extensions/api/_permission_features.json
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -191,9 +191,7 @@
   },
   "contentSettings": {
     "channel": "stable",
-    "extension_types": ["extension", "legacy_packaged_app"],
-    // "desktop_android" is not supported.
-    "platforms": ["chromeos", "linux", "mac", "win"]
+    "extension_types": ["extension", "legacy_packaged_app"]
   },
   "contextMenus": {
     "channel": "stable",
diff --git a/chrome/common/extensions/api/api_sources.gni b/chrome/common/extensions/api/api_sources.gni
index 1bb3d09f..1d45b2e1 100644
--- a/chrome/common/extensions/api/api_sources.gni
+++ b/chrome/common/extensions/api/api_sources.gni
@@ -14,6 +14,7 @@
 schema_sources_ = [
   "activity_log_private.json",
   "bookmarks.json",
+  "content_settings.json",
   "context_menus.json",
   "cookies.json",
   "debugger.json",
@@ -81,7 +82,6 @@
     "bookmark_manager_private.json",
     "braille_display_private.idl",
     "command_line_private.json",
-    "content_settings.json",
     "crash_report_private.idl",
     "enterprise_reporting_private.idl",
     "proxy_override_rules_private.json",
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc b/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
index 33c854a..5adbda1 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
@@ -14,9 +14,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/memory/scoped_refptr.h"
-#include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/test/gmock_callback_support.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/test/base/chrome_render_view_test.h"
 #include "chrome/test/base/chrome_unit_test_suite.h"
@@ -220,11 +218,9 @@
   const auto page_text = MakeRefPtrString(u"dummy");
   const auto page_text2 = MakeRefPtrString(u"dummy2");
   {
-    base::RunLoop run_loop;
-    EXPECT_CALL(*classifier_, BeginClassification(_))
-        .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
+    InSequence s;
+    EXPECT_CALL(*classifier_, BeginClassification(_));
     delegate_->PageCaptured(page_text, false);
-    run_loop.Run();
     Mock::VerifyAndClearExpectations(classifier_);
   }
 
@@ -235,37 +231,20 @@
 
   // Start phishing detection without a fresh page text should still classify,
   // because the top level URL still match.
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(*classifier_, BeginClassification(_))
-        .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
-    OnStartPhishingDetection(url);
-    delegate_->PageCaptured(page_text, false);
-    run_loop.Run();
-    Mock::VerifyAndClearExpectations(classifier_);
-  }
+  EXPECT_CALL(*classifier_, BeginClassification(_));
+  OnStartPhishingDetection(url);
+  delegate_->PageCaptured(page_text, false);
+  Mock::VerifyAndClearExpectations(classifier_);
 
-  // Even if the page text is captured first, it shouldn't matter since the
-  // browser request will start the classification.
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(*classifier_, BeginClassification(_))
-        .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
-    delegate_->PageCaptured(page_text, false);
-    OnStartPhishingDetection(url);
-    run_loop.Run();
-    Mock::VerifyAndClearExpectations(classifier_);
-  }
+  EXPECT_CALL(*classifier_, BeginClassification(_));
+  OnStartPhishingDetection(url);
+  delegate_->PageCaptured(page_text, false);
+  Mock::VerifyAndClearExpectations(classifier_);
 
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(*classifier_, BeginClassification(_))
-        .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
-    OnStartPhishingDetection(url);
-    delegate_->PageCaptured(page_text, false);
-    run_loop.Run();
-    Mock::VerifyAndClearExpectations(classifier_);
-  }
+  EXPECT_CALL(*classifier_, BeginClassification(_));
+  OnStartPhishingDetection(url);
+  delegate_->PageCaptured(page_text, false);
+  Mock::VerifyAndClearExpectations(classifier_);
 
   // Now load a new toplevel page, which should trigger another classification.
   EXPECT_CALL(*classifier_, CancelPendingClassification());
@@ -276,11 +255,9 @@
   OnStartPhishingDetection(new_url);
 
   {
-    base::RunLoop run_loop;
-    EXPECT_CALL(*classifier_, BeginClassification(_))
-        .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
+    InSequence s;
+    EXPECT_CALL(*classifier_, BeginClassification(_));
     delegate_->PageCaptured(page_text2, false);
-    run_loop.Run();
     Mock::VerifyAndClearExpectations(classifier_);
   }
 
@@ -389,20 +366,14 @@
 
   // Now set a scorer, which should cause a classifier to be created, and
   // classification will happen again because the scorer is set within timeout.
-  base::RunLoop run_loop;
-  EXPECT_CALL(*classifier_, BeginClassification(_))
-      .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
+  EXPECT_CALL(*classifier_, BeginClassification(_));
   SetScorer(/*model_version=*/1);
-  run_loop.Run();
   Mock::VerifyAndClearExpectations(classifier_);
 
   // Manually start a classification, so that when a new scorer is set, it
   // should cancel.
-  base::RunLoop run_loop2;
-  EXPECT_CALL(*classifier_, BeginClassification(_))
-      .WillOnce(base::test::RunOnceClosure(run_loop2.QuitClosure()));
+  EXPECT_CALL(*classifier_, BeginClassification(_));
   OnStartPhishingDetection(url2);
-  run_loop2.Run();
 
   // If we set a new scorer while a classification is going on the
   // classification should be cancelled.
@@ -432,24 +403,16 @@
   OnStartPhishingDetection(url);
   delegate_->PageCaptured(page_text, false);
 
-  task_environment_.RunUntilIdle();
-
   // Now set a scorer, which should cause a classifier to be created, and
   // classification will happen again because the scorer is set within timeout.
-  base::RunLoop run_loop;
-  EXPECT_CALL(*classifier_, BeginClassification(_))
-      .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
+  EXPECT_CALL(*classifier_, BeginClassification(_));
   SetScorer(/*model_version=*/1);
-  run_loop.Run();
   Mock::VerifyAndClearExpectations(classifier_);
 
   // Manually start a classification, so that when a new scorer is set, it
   // should cancel.
-  base::RunLoop run_loop2;
-  EXPECT_CALL(*classifier_, BeginClassification(_))
-      .WillOnce(base::test::RunOnceClosure(run_loop2.QuitClosure()));
+  EXPECT_CALL(*classifier_, BeginClassification(_));
   OnStartPhishingDetection(url);
-  run_loop2.Run();
 
   // If we set a new scorer while a classification is going on the
   // classification should be cancelled.
@@ -492,11 +455,8 @@
 
   // Manually start a classification, so that when a new scorer is set, it
   // should cancel.
-  base::RunLoop run_loop;
-  EXPECT_CALL(*classifier_, BeginClassification(_))
-      .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
+  EXPECT_CALL(*classifier_, BeginClassification(_));
   OnStartPhishingDetection(url2);
-  run_loop.Run();
 
   // If we set a new scorer while a classification is going on the
   // classification should be cancelled.
@@ -537,11 +497,8 @@
   Mock::VerifyAndClearExpectations(classifier_);
 
   // Manually start a classification
-  base::RunLoop run_loop;
-  EXPECT_CALL(*classifier_, BeginClassification(_))
-      .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
+  EXPECT_CALL(*classifier_, BeginClassification(_));
   OnStartPhishingDetection(url);
-  run_loop.Run();
 
   // If we set a new scorer while a classification is going on the
   // classification should be cancelled.
@@ -594,11 +551,8 @@
   Mock::VerifyAndClearExpectations(classifier_);
   // Now simulate the StartPhishingDetection IPC.  We expect classification
   // to begin.
-  base::RunLoop run_loop;
-  EXPECT_CALL(*classifier_, BeginClassification(_))
-      .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
+  EXPECT_CALL(*classifier_, BeginClassification(_));
   OnStartPhishingDetection(url);
-  run_loop.Run();
   Mock::VerifyAndClearExpectations(classifier_);
 
   // Now try again, but this time we will navigate the page away before
@@ -632,30 +586,18 @@
   Mock::VerifyAndClearExpectations(classifier_);
   EXPECT_CALL(*classifier_, CancelPendingClassification());
 
-  // Now the redirecting URL HTML has been loaded.
   GURL redir_url("http://host4.com/redir");
   LoadHTMLWithUrlOverride("123", redir_url.spec().c_str());
   Mock::VerifyAndClearExpectations(classifier_);
 
-  // Although the redirecting URL HTML has already loaded, browser requested
-  // from the original URL, but it shouldn't trigger anything, if with new
-  // observers.
-  if (!base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    EXPECT_CALL(*classifier_, BeginClassification(_));
-  }
+  EXPECT_CALL(*classifier_, BeginClassification(_));
   OnStartPhishingDetection(url4);
   page_text = MakeRefPtrString(u"123");
   {
-    base::RunLoop run_loop2;
-    EXPECT_CALL(*classifier_, BeginClassification(_))
-        .WillOnce(base::test::RunOnceClosure(run_loop2.QuitClosure()));
-    // The below essentially called OnStartPhishingDetection by replacing the
-    // URL.
+    InSequence s;
+    EXPECT_CALL(*classifier_, BeginClassification(_));
     SimulateRedirection(redir_url);
-    // Page has finally captured for the redirecting URL. With the layout
-    // complete, it will start classification on landing page.
     delegate_->PageCaptured(page_text, false);
-    run_loop2.Run();
     Mock::VerifyAndClearExpectations(classifier_);
   }
 
@@ -681,11 +623,9 @@
 
   // Once the non-preliminary capture happens, classification should begin.
   {
-    base::RunLoop run_loop;
-    EXPECT_CALL(*classifier_, BeginClassification(_))
-        .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
+    InSequence s;
+    EXPECT_CALL(*classifier_, BeginClassification(_));
     delegate_->PageCaptured(page_text, false);
-    run_loop.Run();
     Mock::VerifyAndClearExpectations(classifier_);
   }
 
@@ -709,11 +649,9 @@
   Mock::VerifyAndClearExpectations(classifier_);
   OnStartPhishingDetection(url);
   {
-    base::RunLoop run_loop;
-    EXPECT_CALL(*classifier_, BeginClassification(_))
-        .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
+    InSequence s;
+    EXPECT_CALL(*classifier_, BeginClassification(_));
     delegate_->PageCaptured(page_text, false);
-    run_loop.Run();
     Mock::VerifyAndClearExpectations(classifier_);
   }
 
@@ -740,11 +678,9 @@
   Mock::VerifyAndClearExpectations(classifier_);
   OnStartPhishingDetection(url);
   {
-    base::RunLoop run_loop;
-    EXPECT_CALL(*classifier_, BeginClassification(_))
-        .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
+    InSequence s;
+    EXPECT_CALL(*classifier_, BeginClassification(_));
     delegate_->PageCaptured(page_text, false);
-    run_loop.Run();
     Mock::VerifyAndClearExpectations(classifier_);
   }
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 9e8cf73..4520fa09 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1608,6 +1608,7 @@
       "../browser/extensions/api/alarms/alarms_apitest.cc",
       "../browser/extensions/api/bookmarks/bookmarks_apitest.cc",
       "../browser/extensions/api/browsing_data/browsing_data_test.cc",
+      "../browser/extensions/api/content_settings/content_settings_apitest.cc",
       "../browser/extensions/api/context_menus/context_menu_apitest.cc",
       "../browser/extensions/api/context_menus/extension_context_menu_browsertest.cc",
       "../browser/extensions/api/cookies/cookies_apitest.cc",
@@ -1743,6 +1744,7 @@
       "//chrome/browser/extensions",
       "//chrome/browser/extensions:test_support",
       "//chrome/browser/extensions/api/bookmarks/test:test_support",
+      "//chrome/browser/permissions",
       "//chrome/browser/prefetch",
       "//chrome/browser/prefs",
       "//chrome/browser/prefs:util",
@@ -4865,7 +4867,6 @@
         "../browser/extensions/api/bluetooth/bluetooth_private_apitest.cc",
         "../browser/extensions/api/braille_display_private/braille_display_private_apitest.cc",
         "../browser/extensions/api/command_line_private/command_line_private_apitest.cc",
-        "../browser/extensions/api/content_settings/content_settings_apitest.cc",
         "../browser/extensions/api/declarative/declarative_apitest.cc",
         "../browser/extensions/api/extension_action/browser_action_apitest.cc",
         "../browser/extensions/api/extension_action/browser_action_browsertest.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/browser/history/StubbedHistoryProvider.java b/chrome/test/android/javatests/src/org/chromium/chrome/browser/history/StubbedHistoryProvider.java
index 9732c20..83933579 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/browser/history/StubbedHistoryProvider.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/browser/history/StubbedHistoryProvider.java
@@ -170,6 +170,18 @@
     }
 
     public static HistoryItem createHistoryItem(int which, long timestamp) {
+        return createHistoryItem(which, timestamp, which == 5, false);
+    }
+
+    /**
+     * @param which Which history item to create.
+     * @param timestamp The timestamp for the item.
+     * @param blockedVisit Whether the visit was blocked.
+     * @param isActorVisit Whether the visit was actor initiated.
+     * @return A new HistoryItem.
+     */
+    public static HistoryItem createHistoryItem(
+            int which, long timestamp, boolean blockedVisit, boolean isActorVisit) {
         long[] nativeTimestamps = {timestamp * 1000};
         String appId = null;
         if (which == 0) {
@@ -180,8 +192,8 @@
                     appId,
                     timestamp,
                     nativeTimestamps,
-                    false,
-                    false);
+                    blockedVisit,
+                    isActorVisit);
         } else if (which == 1) {
             return new HistoryItem(
                     JUnitTestGURLs.EXAMPLE_URL,
@@ -190,8 +202,8 @@
                     appId,
                     timestamp,
                     nativeTimestamps,
-                    false,
-                    false);
+                    blockedVisit,
+                    isActorVisit);
         } else if (which == 2) {
             return new HistoryItem(
                     JUnitTestGURLs.URL_1,
@@ -200,8 +212,8 @@
                     appId,
                     timestamp,
                     nativeTimestamps,
-                    false,
-                    false);
+                    blockedVisit,
+                    isActorVisit);
         } else if (which == 3) {
             return new HistoryItem(
                     JUnitTestGURLs.URL_2,
@@ -210,8 +222,8 @@
                     appId,
                     timestamp,
                     nativeTimestamps,
-                    false,
-                    false);
+                    blockedVisit,
+                    isActorVisit);
         } else if (which == 4) {
             return new HistoryItem(
                     JUnitTestGURLs.URL_3,
@@ -220,8 +232,8 @@
                     appId,
                     timestamp,
                     nativeTimestamps,
-                    false,
-                    false);
+                    blockedVisit,
+                    isActorVisit);
         } else if (which == 5) {
             return new HistoryItem(
                     JUnitTestGURLs.INITIAL_URL,
@@ -230,8 +242,8 @@
                     appId,
                     timestamp,
                     nativeTimestamps,
-                    true,
-                    false);
+                    blockedVisit,
+                    isActorVisit);
         } else {
             return null;
         }
diff --git a/chrome/test/data/webui/history/history_synced_tabs_test.ts b/chrome/test/data/webui/history/history_synced_tabs_test.ts
index e89f771d..a5a7225 100644
--- a/chrome/test/data/webui/history/history_synced_tabs_test.ts
+++ b/chrome/test/data/webui/history/history_synced_tabs_test.ts
@@ -28,9 +28,9 @@
 
 function assertNoSyncedTabsMessageShown(
     manager: HistorySyncedDeviceManagerElement, stringID: string) {
-  assertFalse(manager.$['no-synced-tabs'].hidden);
+  assertFalse(manager.$.noSyncedTabs.hidden);
   const message = loadTimeData.getString(stringID);
-  assertNotEquals(-1, manager.$['no-synced-tabs'].textContent.indexOf(message));
+  assertNotEquals(-1, manager.$.noSyncedTabs.textContent.indexOf(message));
 }
 
 suite('<history-synced-device-manager>', function() {
@@ -267,14 +267,14 @@
       historySync: SyncState.TURNED_OFF,
     });
     await microtasksFinished();
-    assertFalse(element.$['sign-in-guide'].hidden);
+    assertFalse(element.$.signInGuide.hidden);
     webUIListenerCallback('history-identity-state-changed', {
       signIn: HistorySignInState.SIGNED_IN,
       tabsSync: SyncState.TURNED_ON,
       historySync: SyncState.TURNED_OFF,
     });
     await microtasksFinished();
-    assertTrue(element.$['sign-in-guide'].hidden);
+    assertTrue(element.$.signInGuide.hidden);
   });
   // </if>
 
@@ -288,7 +288,7 @@
     });
     element.clearSyncedDevicesForTest();
     await microtasksFinished();
-    assertTrue(element.$['no-synced-tabs'].hidden);
+    assertTrue(element.$.noSyncedTabs.hidden);
 
     let cards = getCards(element);
     assertEquals(0, cards.length);
@@ -318,7 +318,7 @@
     cards = getCards(element);
     assertEquals(1, cards.length);
     // If there are any synced tabs, hide the 'no synced tabs' message.
-    assertTrue(element.$['no-synced-tabs'].hidden);
+    assertTrue(element.$.noSyncedTabs.hidden);
 
     webUIListenerCallback('history-identity-state-changed', {
       signIn: HistorySignInState.SIGNED_OUT,
@@ -327,7 +327,7 @@
     });
     await microtasksFinished();
     // When user signs out, don't show the message.
-    assertTrue(element.$['no-synced-tabs'].hidden);
+    assertTrue(element.$.noSyncedTabs.hidden);
   });
 
   test('hide sign in promo in guest mode', async () => {
@@ -341,7 +341,7 @@
       historySync: SyncState.TURNED_OFF,
     });
     await microtasksFinished();
-    assertTrue(element.$['sign-in-guide'].hidden);
+    assertTrue(element.$.signInGuide.hidden);
   });
 
   test('hide sign-in promo if sign-in is disabled', async function() {
@@ -355,7 +355,7 @@
       guestSession: false,
     });
     await microtasksFinished();
-    assertTrue(element.$['sign-in-guide'].hidden);
+    assertTrue(element.$.signInGuide.hidden);
   });
 
   test('no synced tabs message displays on load', async () => {
@@ -436,7 +436,7 @@
     });
     await microtasksFinished();
     // Should not be visible with kReplaceSyncPromosWithSignInPromos enabled.
-    assertFalse(isChildVisible(element, '#sign-in-guide'));
+    assertFalse(isChildVisible(element, '#signInGuide'));
     // The other states promo elements should not be visible.
     assertFalse(isChildVisible(element, '#signed-in-sync-history-promo-desc'));
     assertFalse(isChildVisible(element, '#verify-its-you-button'));
@@ -502,7 +502,7 @@
     await microtasksFinished();
 
     // The 'no synced tabs' message should be shown.
-    assertTrue(isChildVisible(element, '#no-synced-tabs'));
+    assertTrue(isChildVisible(element, '#noSyncedTabs'));
 
     // The promo elements are not shown
     assertFalse(isChildVisible(element, '#history-sync-optin'));
diff --git a/chrome/test/data/webui/new_tab_page/composebox/composebox_test.ts b/chrome/test/data/webui/new_tab_page/composebox/composebox_test.ts
index 43763db..390d9c7 100644
--- a/chrome/test/data/webui/new_tab_page/composebox/composebox_test.ts
+++ b/chrome/test/data/webui/new_tab_page/composebox/composebox_test.ts
@@ -27,6 +27,8 @@
 const ADD_FILE_CONTEXT_FN = 'addFileContext';
 const ADD_TAB_CONTEXT_FN = 'addTabContext';
 const FAKE_TOKEN_STRING = '00000000000000001234567890ABCDEF';
+const FAKE_TOKEN_STRING_2 = '00000000000000001234567890ABCDEE';
+
 const CONTEXT_ADDED_NTP =
     'ContextualSearch.ContextAdded.ContextAddedMethod.NewTabPage';
 
@@ -160,6 +162,31 @@
     });
   }
 
+  async function addTab() {
+    searchboxHandler.setPromiseResolveFor(
+        ADD_TAB_CONTEXT_FN, FAKE_TOKEN_STRING);
+
+    // Assert no files.
+    assertFalse(!!$$<HTMLElement>(composeboxElement, '#carousel'));
+
+    const contextMenuButton = $$(composeboxElement, '#contextEntrypoint');
+    assertTrue(!!contextMenuButton);
+    const sampleTabTitle = 'Sample Tab';
+    contextMenuButton.dispatchEvent(new CustomEvent('add-tab-context', {
+      detail: {id: 1, title: sampleTabTitle},
+      bubbles: true,
+      composed: true,
+    }));
+
+    await searchboxHandler.whenCalled(ADD_TAB_CONTEXT_FN);
+    await microtasksFinished();
+    const files = composeboxElement.$.carousel.files;
+    assertEquals(files.length, 1);
+    assertEquals(files[0]!.type, 'tab');
+    assertEquals(files[0]!.name, sampleTabTitle);
+    return FAKE_TOKEN_STRING;
+  }
+
   function getInputForFileType(fileType: string): HTMLInputElement {
     return fileType === 'application/pdf' ?
         composeboxElement.$.fileInputs.$.fileInput :
@@ -2091,7 +2118,7 @@
     // Check that only one files were added.
     assertEquals(1, searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN));
 
-    // Check that the "too many files" metric was recorded (Enum value 1).
+    // Check that the 'too many files' metric was recorded (Enum value 1).
     assertEquals(
         1,
         metrics.count(
@@ -2674,27 +2701,7 @@
 
     test('add tab context', async () => {
       createComposeboxElement();
-      searchboxHandler.setPromiseResolveFor(
-          ADD_TAB_CONTEXT_FN, {low: BigInt(1), high: BigInt(2)});
-
-      // Assert no files.
-      assertFalse(!!$$<HTMLElement>(composeboxElement, '#carousel'));
-
-      const contextMenuButton = $$(composeboxElement, '#contextEntrypoint');
-      assertTrue(!!contextMenuButton);
-      const sampleTabTitle = 'Sample Tab';
-      contextMenuButton.dispatchEvent(new CustomEvent('add-tab-context', {
-        detail: {id: 1, title: sampleTabTitle},
-        bubbles: true,
-        composed: true,
-      }));
-
-      await searchboxHandler.whenCalled(ADD_TAB_CONTEXT_FN);
-      await microtasksFinished();
-      const files = composeboxElement.$.carousel.files;
-      assertEquals(files.length, 1);
-      assertEquals(files[0]!.type, 'tab');
-      assertEquals(files[0]!.name, sampleTabTitle);
+      await addTab();
     });
 
     test('add tab context fails', async () => {
@@ -2919,4 +2926,57 @@
         assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1);
         assertEquals(searchboxHandler.getCallCount('stopAutocomplete'), 0);
       });
+
+  test('when flag enabled, adds tab context of ghost file', async () => {
+    createComposeboxElement();
+    document.body.appendChild(composeboxElement);
+    composeboxElement.shouldShowGhostFiles = true;
+
+    await addTab();
+
+    await composeboxElement.updateComplete;
+    await microtasksFinished();
+
+    assertTrue(
+        composeboxElement.getNumOfFilesForTesting() === 1,
+        'Tab should be added');
+
+    const bad_token = FAKE_TOKEN_STRING_2;
+    searchboxCallbackRouterRemote.onContextualInputStatusChanged(
+        bad_token,
+        FileUploadStatus.kUploadSuccessful,
+        null,
+    );
+    await composeboxElement.updateComplete;
+    await microtasksFinished();
+    assertTrue(
+        composeboxElement.getNumOfFilesForTesting() === 2,
+        'Ghost file should be added');
+  });
+
+  test('does not add tab context of ghost file', async () => {
+    createComposeboxElement();
+    document.body.appendChild(composeboxElement);
+    composeboxElement.shouldShowGhostFiles = false;
+
+    await addTab();
+    await composeboxElement.updateComplete;
+    await microtasksFinished();
+
+
+    assertTrue(
+        composeboxElement.getNumOfFilesForTesting() === 1,
+        'Tab should be added');
+    const bad_token = FAKE_TOKEN_STRING_2;
+    searchboxCallbackRouterRemote.onContextualInputStatusChanged(
+        bad_token,
+        FileUploadStatus.kUploadSuccessful,
+        null,
+    );
+    await composeboxElement.updateComplete;
+    await microtasksFinished();
+    assertTrue(
+        composeboxElement.getNumOfFilesForTesting() === 1,
+        'Ghost file should not be added');
+  });
 });
diff --git a/components/browser_apis/browser_controls/BUILD.gn b/components/browser_apis/browser_controls/BUILD.gn
index b1700a67..a841067b8 100644
--- a/components/browser_apis/browser_controls/BUILD.gn
+++ b/components/browser_apis/browser_controls/BUILD.gn
@@ -20,9 +20,6 @@
   public_deps = [
     ":mojom_data_model",
     "//mojo/public/mojom/base",
-    "//ui/base/mojom:ui_base_types",
-    "//ui/gfx/geometry/mojom",
-    "//url/mojom:url_mojom_gurl",
   ]
 
   # Generate TypeScript bindings for WebUI.
diff --git a/components/browser_apis/browser_controls/browser_controls_api.mojom b/components/browser_apis/browser_controls/browser_controls_api.mojom
index 95802cf..a8884c0 100644
--- a/components/browser_apis/browser_controls/browser_controls_api.mojom
+++ b/components/browser_apis/browser_controls/browser_controls_api.mojom
@@ -5,48 +5,14 @@
 module browser_controls_api.mojom;
 
 import "components/browser_apis/browser_controls/browser_controls_api_data_model.mojom";
-import "ui/base/mojom/menu_source_type.mojom";
-import "ui/gfx/geometry/mojom/geometry.mojom";
-import "mojo/public/mojom/base/error.mojom";
-
-// Called from C++ side of chrome://webui-toolbar.top-chrome
-// (Browser -> Renderer)
-interface BrowserControlsObserver {
-  // Observes events for navigation controls (e.g. back, forward, reload
-  // buttons).  |state| contains the state for all navigation controls
-  // so that they can update together to avoid visual flicker.
-  OnNavigationControlsStateChanged(NavigationControlsState state);
-};
-
-struct InitialState {
-    NavigationControlsState state;
-
-    pending_receiver<BrowserControlsObserver> update_stream;
-};
 
 // BrowserControlsService defines methods to be called from the WebUI to the
 // browser-side. This interface consolidates various browser control actions.
 interface BrowserControlsService {
-  // Binds an observer to the service. This is a synchronization point between
-  // the client and the browser. All subsequent updates after the initial
-  // data will be associated receiver.
-  Bind() => result<InitialState, mojo_base.mojom.Error>;
-
   // Stops the current page from loading. If the page is not in a loading
   // state, it is a no-op.
   StopLoad();
 
-  // Displays the context menu with the provided menu type at the provided
-  // offset in CSS pixels relative to the WebUIToolbarWebView's viewport origin.
-  // The source indicates what triggered showing this menu (mouse, keyboard,
-  // etc).
-  ShowContextMenu(ContextMenuType menu_type,
-                  gfx.mojom.Point viewport_coordinate_css_pixels,
-                  ui.mojom.MenuSourceType source);
-
-  // Called when the page finishes initialization.
-  OnPageInitialized();
-
   // Reloads the current page. If the page is already in a loading state,
   // it will trigger another reload, and abort the previous one.
   // If `bypass_cache` is set to true, the page will be reloaded while
diff --git a/components/browser_apis/browser_controls/browser_controls_api_data_model.mojom b/components/browser_apis/browser_controls/browser_controls_api_data_model.mojom
index 31fa55a..31cf4b2 100644
--- a/components/browser_apis/browser_controls/browser_controls_api_data_model.mojom
+++ b/components/browser_apis/browser_controls/browser_controls_api_data_model.mojom
@@ -12,44 +12,3 @@
   kAltKeyDown = 2,
   kMetaKeyDown = 3,
 };
-
-// The context menu types that can be opened with BrowserControlsService's
-// ShowContextMenu.
-enum ContextMenuType {
-  kUnspecified = 0,
-  kReload = 1,
-  kSplitTabsAction = 2,
-  kSplitTabsContext = 3,
-};
-
-struct ReloadControlState {
-  bool is_devtools_connected;
-  bool is_navigation_loading;
-  bool is_context_menu_visible;
-};
-
-enum SplitTabActiveLocation {
-  kStart,
-  kEnd,
-  kTop,
-  kBottom,
-};
-
-enum ToolbarButtonType {
-  kSplitTabs,
-};
-
-struct SplitTabsControlState {
-  bool is_current_tab_split;
-  SplitTabActiveLocation location;
-  bool is_pinned;
-  bool is_context_menu_visible;
-};
-
-struct NavigationControlsState {
-  ReloadControlState reload_control_state;
-  SplitTabsControlState split_tabs_control_state;
-  // This is incremented every time a settings change may change the layout
-  // constants.
-  int32 layout_constants_version = 0;
-};
diff --git a/components/browser_apis/ui_controllers/toolbar/BUILD.gn b/components/browser_apis/ui_controllers/toolbar/BUILD.gn
new file mode 100644
index 0000000..6edab3c
--- /dev/null
+++ b/components/browser_apis/ui_controllers/toolbar/BUILD.gn
@@ -0,0 +1,33 @@
+# Copyright 2026 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# Generate Mojo bindings for toolbar_ui_api_data_model.mojom.
+mojom("mojom_data_model") {
+  sources = [ "toolbar_ui_api_data_model.mojom" ]
+
+  # Generate TypeScript bindings for WebUI.
+  webui_module_path = "/"
+}
+
+# Generate Mojo bindings for toolbar_ui_api.mojom.
+mojom("mojom") {
+  sources = [ "toolbar_ui_api.mojom" ]
+
+  # Dependencies on other mojom targets.
+  public_deps = [
+    ":mojom_data_model",
+    "//mojo/public/mojom/base",
+    "//ui/base/mojom:ui_base_types",
+    "//ui/gfx/geometry/mojom",
+  ]
+
+  # Generate TypeScript bindings for WebUI.
+  webui_module_path = "/"
+}
+
+source_set("unit_tests") {
+  testonly = true
+}
diff --git a/components/browser_apis/ui_controllers/toolbar/OWNERS b/components/browser_apis/ui_controllers/toolbar/OWNERS
new file mode 100644
index 0000000..92d932cf
--- /dev/null
+++ b/components/browser_apis/ui_controllers/toolbar/OWNERS
@@ -0,0 +1,5 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+
+pauljensen@google.com
+xtlsheep@google.com
diff --git a/components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom b/components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom
new file mode 100644
index 0000000..58281f3
--- /dev/null
+++ b/components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom
@@ -0,0 +1,43 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module toolbar_ui_api.mojom;
+
+import "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom";
+import "ui/base/mojom/menu_source_type.mojom";
+import "ui/gfx/geometry/mojom/geometry.mojom";
+import "mojo/public/mojom/base/error.mojom";
+
+// Used by Toolbar UI to observe navigation controls state changes.
+interface ToolbarUIObserver {
+  // Observes events for navigation controls (e.g. back, forward, reload
+  // buttons). |state| contains the state for all navigation controls
+  // so that they can update together to avoid visual flicker.
+  OnNavigationControlsStateChanged(NavigationControlsState state);
+};
+
+struct InitialState {
+    NavigationControlsState state;
+    pending_receiver<ToolbarUIObserver> update_stream;
+};
+
+// ToolbarUIService defines methods to be called from the WebUI to the
+// browser-side to drive UI logic.
+interface ToolbarUIService {
+  // Binds an observer to the service. This is a synchronization point between
+  // the client and the browser. All subsequent updates after the initial
+  // data will be associated receiver.
+  Bind() => result<InitialState, mojo_base.mojom.Error>;
+
+  // Displays the context menu with the provided menu type at the provided
+  // offset in CSS pixels relative to the WebUIToolbarWebView's viewport origin.
+  // The source indicates what triggered showing this menu (mouse, keyboard,
+  // etc).
+  ShowContextMenu(ContextMenuType menu_type,
+                  gfx.mojom.Point viewport_coordinate_css_pixels,
+                  ui.mojom.MenuSourceType source);
+
+  // Called when the page finishes initialization.
+  OnPageInitialized();
+};
diff --git a/components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom b/components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom
new file mode 100644
index 0000000..ef5ee3e
--- /dev/null
+++ b/components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom
@@ -0,0 +1,46 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module toolbar_ui_api.mojom;
+
+// The context menu types that can be opened with ToolbarUIService's
+// ShowContextMenu.
+enum ContextMenuType {
+  kUnspecified = 0,
+  kReload = 1,
+  kSplitTabsAction = 2,
+  kSplitTabsContext = 3,
+};
+
+enum SplitTabActiveLocation {
+  kStart,
+  kEnd,
+  kTop,
+  kBottom,
+};
+
+enum ToolbarButtonType {
+  kSplitTabs,
+};
+
+struct ReloadControlState {
+  bool is_devtools_connected;
+  bool is_navigation_loading;
+  bool is_context_menu_visible;
+};
+
+struct SplitTabsControlState {
+  bool is_current_tab_split;
+  SplitTabActiveLocation location;
+  bool is_pinned;
+  bool is_context_menu_visible;
+};
+
+struct NavigationControlsState {
+  ReloadControlState reload_control_state;
+  SplitTabsControlState split_tabs_control_state;
+  // This is incremented every time a settings change may change the layout
+  // constants.
+  int32 layout_constants_version = 0;
+};
diff --git a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImpl.java b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImpl.java
index ee2739f..bce37e6 100644
--- a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImpl.java
+++ b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImpl.java
@@ -118,7 +118,7 @@
     private @Nullable BottomSheetContent mContentWhenSuppressed;
 
     private int mAppHeaderHeight;
-    private int mBottomControlsHeight;
+    private int mBottomControlsOffset;
     private boolean mIsAnchoredToBottomControls;
 
     /**
@@ -232,7 +232,7 @@
                 mAlwaysFullWidth,
                 mEdgeToEdgeBottomInsetSupplier,
                 mAppHeaderHeight,
-                mBottomControlsHeight);
+                mBottomControlsOffset);
 
         // Initialize the queue with a comparator that checks content priority.
         mContentQueue =
@@ -348,9 +348,9 @@
     }
 
     @Override
-    public void setBottomControlsHeight(int bottomControlsHeight) {
-        if (mBottomControlsHeight == bottomControlsHeight) return;
-        mBottomControlsHeight = bottomControlsHeight;
+    public void setBottomControlsOffset(int bottomControlsOffset) {
+        if (mBottomControlsOffset == bottomControlsOffset) return;
+        mBottomControlsOffset = bottomControlsOffset;
         var scrimManager = mScrimManagerSupplier.get();
         if (scrimManager != null) {
             // Set the appropriate offset for the current scrim state.
@@ -745,7 +745,7 @@
             // the bottom controls.
             mIsAnchoredToBottomControls = true;
             mBottomSheetContainer.setZ(0.0f);
-            mBottomSheet.setBottomMargin(mBottomControlsHeight);
+            mBottomSheet.setBottomMargin(mBottomControlsOffset);
         }
     }
 
diff --git a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImplUnitTest.java b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImplUnitTest.java
index a2f0e0ff..416c3a8df 100644
--- a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImplUnitTest.java
+++ b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImplUnitTest.java
@@ -148,10 +148,10 @@
     }
 
     @Test
-    public void testBottomControlsHeight() {
+    public void testBottomControlsOffset() {
         mController.runSheetInitializerForTesting();
         doReturn(true).when(mBottomSheet).isSheetOpen();
-        mController.setBottomControlsHeight(100);
+        mController.setBottomControlsOffset(100);
 
         verify(mBottomSheet).setBottomMargin(100);
 
diff --git a/components/browser_ui/bottomsheet/android/java/src/org/chromium/components/browser_ui/bottomsheet/ManagedBottomSheetController.java b/components/browser_ui/bottomsheet/android/java/src/org/chromium/components/browser_ui/bottomsheet/ManagedBottomSheetController.java
index 25e1803..2e4854a 100644
--- a/components/browser_ui/bottomsheet/android/java/src/org/chromium/components/browser_ui/bottomsheet/ManagedBottomSheetController.java
+++ b/components/browser_ui/bottomsheet/android/java/src/org/chromium/components/browser_ui/bottomsheet/ManagedBottomSheetController.java
@@ -51,8 +51,8 @@
      */
     void setBrowserControlsHiddenRatio(float ratio);
 
-    /** Set the current height of the bottom controls. */
-    void setBottomControlsHeight(int bottomControlsHeight);
+    /** Set the current offset of the bottom controls. */
+    void setBottomControlsOffset(int bottomControlsOffset);
 
     /** Clean up any state maintained by the controller. */
     void destroy();
diff --git a/components/contextual_search/contextual_search_types.h b/components/contextual_search/contextual_search_types.h
index 55a8510a..86eb83d 100644
--- a/components/contextual_search/contextual_search_types.h
+++ b/components/contextual_search/contextual_search_types.h
@@ -23,7 +23,7 @@
 
 // Upload status of a file.
 // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.contextual_search
-enum class FileUploadStatus {
+enum class ContextUploadStatus {
   // Not uploaded.
   kNotUploaded = 0,
   // File being processed.
@@ -44,8 +44,10 @@
   kUploadReplaced = 8,
 };
 
+using FileUploadStatus = ContextUploadStatus;
+
 // For upload error notifications and metrics.
-enum class FileUploadErrorType {
+enum class ContextUploadErrorType {
   // Unknown.
   kUnknown = 0,
   // Browser error before/during request, not covered by validation.
@@ -62,6 +64,8 @@
   kImageProcessingError = 6,
 };
 
+using FileUploadErrorType = ContextUploadErrorType;
+
 // Struct containing file information for a file upload.
 struct FileInfo {
  public:
@@ -93,13 +97,13 @@
 
   // The upload status of the file.
   // Do not modify this field directly.
-  contextual_search::FileUploadStatus upload_status =
-      contextual_search::FileUploadStatus::kNotUploaded;
+  contextual_search::ContextUploadStatus upload_status =
+      contextual_search::ContextUploadStatus::kNotUploaded;
 
   // The error type if the upload failed.
   // Do not modify this field directly.
-  contextual_search::FileUploadErrorType upload_error_type =
-      contextual_search::FileUploadErrorType::kUnknown;
+  contextual_search::ContextUploadErrorType upload_error_type =
+      contextual_search::ContextUploadErrorType::kUnknown;
 
   // If populated, the url of the tab corresponding to this uploaded file.
   std::optional<GURL> tab_url;
diff --git a/components/contextual_search/internal/composebox_query_controller.cc b/components/contextual_search/internal/composebox_query_controller.cc
index f68c812a..df6a0fed 100644
--- a/components/contextual_search/internal/composebox_query_controller.cc
+++ b/components/contextual_search/internal/composebox_query_controller.cc
@@ -512,8 +512,8 @@
 
       // Add the "cvst" lns mode param to the url.
       if (is_aim_search) {
-        search_url_request_info->additional_params.insert(
-            {kLnsModeQueryParameterKey, kLnsModeQueryParameterValue});
+        search_url_request_info->additional_params[kLnsModeQueryParameterKey] =
+            kLnsModeQueryParameterValue;
       }
 
       // Get the encoded visual search interaction log data.
diff --git a/components/contextual_search/internal/composebox_query_controller_unittest.cc b/components/contextual_search/internal/composebox_query_controller_unittest.cc
index 481f0fb..2dd1501 100644
--- a/components/contextual_search/internal/composebox_query_controller_unittest.cc
+++ b/components/contextual_search/internal/composebox_query_controller_unittest.cc
@@ -4570,6 +4570,46 @@
 }
 
 TEST_F(ComposeboxQueryControllerTest,
+       CreateSearchUrl_AimSearch_OverwritesLnsModeIfPresent) {
+  // Act: Start the session.
+  controller().InitializeIfNeeded();
+
+  // Assert: Validate cluster info request and state changes.
+  WaitForClusterInfo();
+
+  // Act: Start the file upload flow.
+  const base::UnguessableToken file_token = base::UnguessableToken::Create();
+  StartPdfFileUploadFlow(file_token,
+                         /*file_data=*/std::vector<uint8_t>());
+
+  // Assert: Validate file upload request and status changes.
+  WaitForFileUpload(file_token, lens::MimeType::kPdf);
+
+  // Act: Create the destination URL for the query.
+  std::unique_ptr<CreateSearchUrlRequestInfo> search_url_request_info =
+      std::make_unique<CreateSearchUrlRequestInfo>();
+  search_url_request_info->query_text = "hello";
+  search_url_request_info->search_url_type =
+      ComposeboxQueryController::SearchUrlType::kAim;
+  search_url_request_info->query_start_time = kTestQueryStartTime;
+  search_url_request_info->file_tokens.push_back(file_token);
+  // Add an existing lns_mode param with a different value.
+  search_url_request_info->additional_params[kLnsModeQueryParameterKey] =
+      "old_value";
+
+  base::test::TestFuture<GURL> url_future;
+  controller().CreateSearchUrl(std::move(search_url_request_info),
+                               url_future.GetCallback());
+  GURL aim_url = url_future.Take();
+
+  // Assert: lns_mode param is present and updated.
+  std::string lns_mode_value;
+  EXPECT_TRUE(net::GetValueForKeyInQuery(aim_url, kLnsModeQueryParameterKey,
+                                         &lns_mode_value));
+  EXPECT_EQ(lns_mode_value, "cvst");
+}
+
+TEST_F(ComposeboxQueryControllerTest,
        CreateAddedInputs_IncludesFilesWithoutLensUsageIntent) {
   // Act: Start the session.
   controller().InitializeIfNeeded();
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index 2e69855..a7d5c8b 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit 2e698554fb2f4d1d3e5a8d6cfce58886a6d43654
+Subproject commit a7d5c8b5113456c2a50f4af09f30f50acd27c707
diff --git a/components/safe_browsing/content/browser/client_side_detection_host.cc b/components/safe_browsing/content/browser/client_side_detection_host.cc
index c311eb2..d680a5a5 100644
--- a/components/safe_browsing/content/browser/client_side_detection_host.cc
+++ b/components/safe_browsing/content/browser/client_side_detection_host.cc
@@ -946,35 +946,10 @@
   // ping back but only cancel the showing of the interstitial.
   weak_factory_.InvalidateWeakPtrs();
 
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    did_first_visually_non_empty_paint_ = false;
-    on_first_contentful_paint_ = false;
-    trigger_model_request_sent_as_force_request_ = false;
-    return;
-  }
-
   trigger_model_request_sent_as_force_request_ = false;
   MaybeStartPreClassification(ClientSideDetectionType::TRIGGER_MODELS);
 }
 
-void ClientSideDetectionHost::DidFirstVisuallyNonEmptyPaint() {
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    did_first_visually_non_empty_paint_ = true;
-    if (on_first_contentful_paint_) {
-      MaybeStartPreClassification(ClientSideDetectionType::TRIGGER_MODELS);
-    }
-  }
-}
-
-void ClientSideDetectionHost::OnFirstContentfulPaintInPrimaryMainFrame() {
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    on_first_contentful_paint_ = true;
-    if (did_first_visually_non_empty_paint_) {
-      MaybeStartPreClassification(ClientSideDetectionType::TRIGGER_MODELS);
-    }
-  }
-}
-
 void ClientSideDetectionHost::OnPromptAdded() {
   if (!IsEnhancedProtectionEnabled(*delegate_->GetPrefs())) {
     return;
@@ -2227,11 +2202,6 @@
           ? "ConditionalImageResize.Enabled"
           : "ConditionalImageResize.Control");
 
-  verdict->mutable_population()->add_finch_active_groups(
-      base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)
-          ? "ClientSideDetectionNewObservers.Enabled"
-          : "ClientSideDetectionNewObservers.Control");
-
   raw_ptr<VerdictCacheManager> cache_manager = delegate_->GetCacheManager();
   if (cache_manager) {
     ChromeUserPopulation::PageLoadToken token =
diff --git a/components/safe_browsing/content/browser/client_side_detection_host.h b/components/safe_browsing/content/browser/client_side_detection_host.h
index 0cd1906f..7a20e8f 100644
--- a/components/safe_browsing/content/browser/client_side_detection_host.h
+++ b/components/safe_browsing/content/browser/client_side_detection_host.h
@@ -153,8 +153,6 @@
   void VibrationRequested() override;
   void OnTextCopiedToClipboard(content::RenderFrameHost* render_frame_host,
                                const std::u16string& copied_text) override;
-  void DidFirstVisuallyNonEmptyPaint() override;
-  void OnFirstContentfulPaintInPrimaryMainFrame() override;
 
   // permissions::PermissionRequestManager::Observer methods:
   void OnPromptAdded() override;
@@ -549,18 +547,6 @@
   // fullscreen.
   GURL last_fullscreen_url_;
 
-  // `did_first_visually_non_empty_paint_` becomes true after the first paint
-  // that is not the background color. `on_first_contentful_paint_` becomes
-  // true after the browser renders the first content from the DOM (e.g.,
-  // text or an image).
-  //
-  // Client-side detection for TRIGGER_MODELS will only start after both events
-  // have occurred. This ensures that classification doesn't begin before the
-  // page has meaningfully rendered. These flags are reset on each new main
-  // frame navigation.
-  bool did_first_visually_non_empty_paint_ = false;
-  bool on_first_contentful_paint_ = false;
-
   // Records the start time of when image embedding started.
   base::TimeTicks image_embedding_start_time_;
   raw_ptr<const base::TickClock> tick_clock_;
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.cc b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.cc
index de5f83b..16d0392 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.cc
@@ -139,29 +139,9 @@
   classifier_->SetClientSideDetectionType(request_type);
   RecordEvent(SBPhishingClassifierEvent::kPhishingDetectionRequested);
 
-  if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    // Browser request has come in, but renderer has not fully loaded, so leave
-    // it up for renderer load to start the classification.
-    if (!renderer_layout_finished_) {
-      return;
-    }
-
-    if (request_type_ == mojom::ClientSideDetectionType::kImageEmbeddingMatch ||
-        request_type_ == mojom::ClientSideDetectionType::kTriggerModels) {
-      base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
-          FROM_HERE,
-          base::BindOnce(&PhishingClassifierDelegate::MaybeStartClassification,
-                         weak_factory_.GetWeakPtr()),
-          base::Seconds(kCsdClassificationDelay.Get()));
-    } else {
-      MaybeStartClassification();
-    }
-
-  } else {
-    // Start classifying the current page if all conditions are met.
-    // See MaybeStartClassification() for details.
-    MaybeStartClassification();
-  }
+  // Start classifying the current page if all conditions are met.
+  // See MaybeStartClassification() for details.
+  MaybeStartClassification();
 }
 
 void PhishingClassifierDelegate::DidCommitProvisionalLoad(
@@ -169,7 +149,6 @@
   blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
   // A new page is starting to load, so cancel classificaiton.
   CancelPendingClassification(CancelClassificationReason::kNavigateAway);
-  renderer_layout_finished_ = false;
   if (!frame->Parent())
     last_main_frame_transition_ = transition;
 }
@@ -181,70 +160,24 @@
 void PhishingClassifierDelegate::PageCaptured(
     scoped_refptr<const base::RefCountedString16> page_text,
     bool preliminary_capture) {
-  if (!base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) {
-    RecordEvent(SBPhishingClassifierEvent::kPageTextCaptured);
+  RecordEvent(SBPhishingClassifierEvent::kPageTextCaptured);
 
-    if (preliminary_capture) {
-      return;
-    }
-
-    // Note: Currently, if the url hasn't changed, we won't restart
-    // classification in this case.  We may want to adjust this.
-
-    last_finished_load_url_ =
-        render_frame()->GetWebFrame()->GetDocument().Url();
-
-    GURL stripped_last_load_url(StripRef(last_finished_load_url_));
-    // Check if toplevel URL has changed.
-    if (stripped_last_load_url == StripRef(last_url_sent_to_classifier_)) {
-      return;
-    }
-
-    MaybeStartClassification();
-  } else {
-    // This is true if layout_type == kWebMeaningfulLayout::kFinishedParsing.
-    // We are looking for kWebMeaningfulLayout::kFinishedLoading only.
-    // PageCaptured is not called for any other cases of kWebMeaningfulLayout.
-    if (preliminary_capture) {
-      return;
-    }
-    renderer_layout_finished_ = true;
-    RecordEvent(
-        SBPhishingClassifierEvent::kPhishingClassifierPageFinishedLoading);
-    // Note: Currently, if the url hasn't changed, we won't restart
-    // classification in this case.  We may want to adjust this.
-    last_finished_load_url_ =
-        render_frame()->GetWebFrame()->GetDocument().Url();
-
-    // Browser side has not made a request yet, so no need to try to start the
-    // classification.
-    if (!is_phishing_detection_running_) {
-      return;
-    }
-
-    GURL stripped_last_load_url(StripRef(last_finished_load_url_));
-    // If we're classifying at the moment and there's a new finished load on the
-    // page, do not attempt to start a new classification.
-    if (is_classifying_ &&
-        stripped_last_load_url == StripRef(last_url_sent_to_classifier_)) {
-      RecordEvent(
-          SBPhishingClassifierEvent::
-              kPhishingClassifierPageFinishedLoadingAgainDuringClassification);
-      return;
-    }
-
-    if (request_type_ == mojom::ClientSideDetectionType::kTriggerModels ||
-        request_type_ == mojom::ClientSideDetectionType::kImageEmbeddingMatch) {
-      base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
-          FROM_HERE,
-          base::BindOnce(&PhishingClassifierDelegate::MaybeStartClassification,
-                         weak_factory_.GetWeakPtr()),
-          base::Seconds(kCsdClassificationDelay.Get()));
-
-    } else {
-      MaybeStartClassification();
-    }
+  if (preliminary_capture) {
+    return;
   }
+
+  // Note: Currently, if the url hasn't changed, we won't restart
+  // classification in this case.  We may want to adjust this.
+
+  last_finished_load_url_ = render_frame()->GetWebFrame()->GetDocument().Url();
+
+  GURL stripped_last_load_url(StripRef(last_finished_load_url_));
+  // Check if toplevel URL has changed.
+  if (stripped_last_load_url == StripRef(last_url_sent_to_classifier_)) {
+    return;
+  }
+
+  MaybeStartClassification();
 }
 
 void PhishingClassifierDelegate::CancelPendingClassification(
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.h b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.h
index d690cfb..d45fedab 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.h
+++ b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.h
@@ -51,12 +51,7 @@
   kPhishingClasifierCallbackEmptyOnCompletion = 8,
   // Phishing classification request responded.
   kPhishingClassifierRequestResponded = 9,
-  // Renderer frame layout has finished loading as
-  // WebMeaningfulLayout::kFinishedLoading.
-  kPhishingClassifierPageFinishedLoading = 10,
-  // Renderer frame layout has finished loading during classification.
-  kPhishingClassifierPageFinishedLoadingAgainDuringClassification = 11,
-  kMaxValue = kPhishingClassifierPageFinishedLoadingAgainDuringClassification,
+  kMaxValue = kPhishingClassifierRequestResponded,
 };
 
 class PhishingClassifierDelegate : public content::RenderFrameObserver,
@@ -179,10 +174,6 @@
   // set to false whenever phishing detection has finished.
   bool is_phishing_detection_running_ = false;
 
-  // Set to true when PageText is captured. Set to false when there is a new
-  // frame loading. This is used as a signal to start the classification.
-  bool renderer_layout_finished_ = false;
-
   // Set to true when we want to classify for the page, but classifier was not
   // ready. It is set to false whenever |is_phishing_detection_running_| is set
   // to true, classification is happening, completed, or cancelled.
diff --git a/components/safe_browsing/core/common/features.cc b/components/safe_browsing/core/common/features.cc
index e2fb332..1156893 100644
--- a/components/safe_browsing/core/common/features.cc
+++ b/components/safe_browsing/core/common/features.cc
@@ -133,11 +133,6 @@
 BASE_FEATURE(kClientSideDetectionLlamaForcedTriggerInfoForScamDetection,
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kClientSideDetectionNewObservers,
-             base::FEATURE_DISABLED_BY_DEFAULT);
-constexpr base::FeatureParam<double> kCsdClassificationDelay{
-    &kClientSideDetectionNewObservers, "ClassificationDelay", 0.0};
-
 #if BUILDFLAG(IS_ANDROID)
 BASE_FEATURE(kClientSideDetectionOnDeviceModelLazyDownloadAndroid,
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/components/safe_browsing/core/common/features.h b/components/safe_browsing/core/common/features.h
index 454249330..061b070 100644
--- a/components/safe_browsing/core/common/features.h
+++ b/components/safe_browsing/core/common/features.h
@@ -120,11 +120,6 @@
 BASE_DECLARE_FEATURE(
     kClientSideDetectionLlamaForcedTriggerInfoForScamDetection);
 
-// The observers that trigger the image classification have been tweaked with a
-// more defined page loading state check.
-BASE_DECLARE_FEATURE(kClientSideDetectionNewObservers);
-extern const base::FeatureParam<double> kCsdClassificationDelay;
-
 #if BUILDFLAG(IS_ANDROID)
 // Instead of starting model download on startup, do it lazily during inference.
 BASE_DECLARE_FEATURE(kClientSideDetectionOnDeviceModelLazyDownloadAndroid);
diff --git a/components/services/storage/dom_storage/session_storage_impl.cc b/components/services/storage/dom_storage/session_storage_impl.cc
index ad3ccbb..4d5320d1 100644
--- a/components/services/storage/dom_storage/session_storage_impl.cc
+++ b/components/services/storage/dom_storage/session_storage_impl.cc
@@ -174,8 +174,6 @@
 }
 
 void SessionStorageImpl::CreateNamespace(const std::string& namespace_id) {
-  CHECK_NE(connection_state_, CONNECTION_IN_PROGRESS,
-           base::NotFatalUntil::M146);
   if (namespaces_.find(namespace_id) != namespaces_.end())
     return;
 
@@ -187,8 +185,6 @@
     const std::string& clone_from_namespace_id,
     const std::string& clone_to_namespace_id,
     mojom::SessionStorageCloneType clone_type) {
-  CHECK_NE(connection_state_, CONNECTION_IN_PROGRESS,
-           base::NotFatalUntil::M146);
   if (namespaces_.find(clone_to_namespace_id) != namespaces_.end()) {
     // Non-immediate clones expect to be paired with a |Clone| from the mojo
     // namespace object. If that clone has already happened, then we don't need
@@ -224,8 +220,8 @@
             clone_to_namespace_id);
       } else if (metadata_.namespace_storage_key_map().contains(
                      clone_from_namespace_id)) {
-        CHECK_EQ(connection_state_, CONNECTION_FINISHED,
-                 base::NotFatalUntil::M146);
+        CHECK_EQ(connection_state_, CONNECTION_FINISHED);
+
         // The namespace exists on disk but is not in-use, so do the appropriate
         // metadata operations to clone the namespace and set up the new object.
         auto source_namespace_entry =
@@ -254,8 +250,6 @@
 
 void SessionStorageImpl::DeleteNamespace(const std::string& namespace_id,
                                          bool should_persist) {
-  CHECK_NE(connection_state_, CONNECTION_IN_PROGRESS,
-           base::NotFatalUntil::M146);
   auto namespace_it = namespaces_.find(namespace_id);
   // If the namespace has pending clones, do the clone now before destroying it.
   if (namespace_it != namespaces_.end()) {
@@ -556,6 +550,8 @@
 scoped_refptr<DomStorageDatabase::SharedMapLocator>
 SessionStorageImpl::RegisterNewAreaMap(const std::string& namespace_id,
                                        const blink::StorageKey& storage_key) {
+  CHECK_EQ(connection_state_, CONNECTION_FINISHED);
+
   scoped_refptr<DomStorageDatabase::SharedMapLocator> map_entry =
       metadata_.RegisterNewMap(namespace_id, storage_key);
   if (database_) {
@@ -595,6 +591,14 @@
     commit_error_count_ = 0;
     return;
   }
+
+  if (connection_state_ != CONNECTION_FINISHED) {
+    // Previous commit errors deleted and recreated the database below.  Ignore
+    // additional errors from the old database while waiting for the new
+    // database to open.
+    return;
+  }
+
   commit_error_count_++;
   if (commit_error_count_ > kSessionStorageCommitErrorThreshold) {
     if (tried_to_recover_from_commit_errors_) {
@@ -635,8 +639,8 @@
     }
   }
 
-  CHECK_EQ(connection_state_, CONNECTION_FINISHED, base::NotFatalUntil::M146);
-  DCHECK_EQ(connection_state_, CONNECTION_FINISHED);
+  CHECK_EQ(connection_state_, CONNECTION_FINISHED);
+
   auto source_namespace_entry =
       metadata_.GetOrCreateNamespaceEntry(source_namespace_id);
   auto namespace_entry = metadata_.GetOrCreateNamespaceEntry(new_namespace_id);
@@ -677,7 +681,7 @@
 
 void SessionStorageImpl::DeleteNamespacesFromMetadataAndDatabase(
     std::vector<std::string> namespace_ids) {
-  CHECK_EQ(connection_state_, CONNECTION_FINISHED, base::NotFatalUntil::M146);
+  CHECK_EQ(connection_state_, CONNECTION_FINISHED);
 
   // Remove each namespace from `metadata_`.
   std::vector<DomStorageDatabase::MapLocator> maps_to_delete;
@@ -724,8 +728,7 @@
 }
 
 void SessionStorageImpl::InitiateConnection(bool in_memory_only) {
-  CHECK_EQ(connection_state_, CONNECTION_IN_PROGRESS,
-           base::NotFatalUntil::M146);
+  CHECK_EQ(connection_state_, CONNECTION_IN_PROGRESS);
 
   if (backing_mode_ != BackingMode::kNoDisk && !in_memory_only &&
       !storage_partition_directory_.empty()) {
@@ -784,8 +787,7 @@
 }
 
 void SessionStorageImpl::OnConnectionFinished() {
-  CHECK(!database_ || connection_state_ == CONNECTION_IN_PROGRESS,
-        base::NotFatalUntil::M146);
+  CHECK_EQ(connection_state_, CONNECTION_IN_PROGRESS);
 
   // If connection was opened successfully, reset tried_to_recreate_during_open_
   // to enable recreating the database on future errors.
diff --git a/components/services/storage/dom_storage/session_storage_impl.h b/components/services/storage/dom_storage/session_storage_impl.h
index dde7757..48b0aa1 100644
--- a/components/services/storage/dom_storage/session_storage_impl.h
+++ b/components/services/storage/dom_storage/session_storage_impl.h
@@ -165,7 +165,9 @@
   // Initiates connecting to the database if no connection is in progress yet.
   void RunWhenConnected(base::OnceClosure callback);
 
-  // Part of our asynchronous directory opening called from RunWhenConnected().
+  // Part of asynchronous database opening called from `RunWhenConnected()`. If
+  // opening the database on disk fails twice, falls back to in memory. If
+  // opening the database in memory fails, runs without a database.
   void InitiateConnection(bool in_memory_only = false);
   void OnDatabaseOpened(DbStatus status);
   void OnGotDatabaseMetadata(
@@ -207,6 +209,7 @@
 
   mojo::Receiver<mojom::SessionStorageControl> receiver_;
 
+  // `database_` is null after failing to open repeatedly.
   std::unique_ptr<AsyncDomStorageDatabase> database_;
   // This can be true even if the profile is not in-memory, since we attempt
   // to create an in-memory DB if on-disk fails. This variable has no meaning
diff --git a/components/update_client/background_downloader_win.cc b/components/update_client/background_downloader_win.cc
index 3adbb99..227b7bd 100644
--- a/components/update_client/background_downloader_win.cc
+++ b/components/update_client/background_downloader_win.cc
@@ -909,29 +909,14 @@
 
 void BackgroundDownloader::CleanupStaleDownloads() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(com_sequence_checker_);
-  EnumerateDownloadDirs(
-      base::StrCat({prod_id_, kDownloadDirectoryPrefixMatcher}),
-      [](const base::FilePath& dir) {
-        const base::Time now = base::Time::Now();
-        base::File::Info info;
-        if (base::GetFileInfo(dir, &info) &&
-            info.creation_time + base::Days(kPurgeStaleJobsAfterDays) < now) {
-          RetryFileOperation(&base::DeletePathRecursively, dir);
-        }
-      });
-}
 
-void BackgroundDownloader::EnumerateDownloadDirs(
-    const base::FilePath::StringType& matcher,
-    base::FunctionRef<void(const base::FilePath& dir)> callback) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(com_sequence_checker_);
   base::FilePath dir;
-  if (base::GetSecureTempDirectory(&dir)) {
-    base::FileEnumerator(dir,
-                         /*recursive=*/false, base::FileEnumerator::DIRECTORIES,
-                         matcher)
-        .ForEach(callback);
+  if (!base::GetSecureTempDirectory(&dir)) {
+    return;
   }
+  CleanupDirectoriesOlderThan(
+      dir, base::StrCat({prod_id_, kDownloadDirectoryPrefixMatcher}),
+      base::Days(kPurgeStaleJobsAfterDays));
 }
 
 }  // namespace update_client
diff --git a/components/update_client/background_downloader_win.h b/components/update_client/background_downloader_win.h
index 0f645f2..596869b 100644
--- a/components/update_client/background_downloader_win.h
+++ b/components/update_client/background_downloader_win.h
@@ -118,11 +118,6 @@
   // Perform a best-effort cleanup up downloads that are too old.
   void CleanupStaleDownloads();
 
-  // Enumerate the writable temporary directories matching |matcher|.
-  void EnumerateDownloadDirs(
-      const base::FilePath::StringType& matcher,
-      base::FunctionRef<void(const base::FilePath& dir)> callback);
-
   // This sequence checker is bound to the main sequence.
   SEQUENCE_CHECKER(sequence_checker_);
   SEQUENCE_CHECKER(com_sequence_checker_);
diff --git a/components/update_client/background_downloader_win_unittest.cc b/components/update_client/background_downloader_win_unittest.cc
index b986d72..1cde5f38 100644
--- a/components/update_client/background_downloader_win_unittest.cc
+++ b/components/update_client/background_downloader_win_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/test/task_environment.h"
 #include "base/win/windows_types.h"
+#include "components/update_client/utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace update_client {
@@ -35,9 +36,9 @@
 };
 
 void BackgroundDownloaderWinTest::TearDown() {
-  downloader_->EnumerateDownloadDirs(
-      kTestDirMatcher,
-      [](const base::FilePath& dir) { base::DeletePathRecursively(dir); });
+  base::FilePath dir;
+  ASSERT_TRUE(base::GetSecureTempDirectory(&dir));
+  CleanupDirectoriesOlderThan(dir, kTestDirMatcher, base::Seconds(0));
 }
 
 TEST_F(BackgroundDownloaderWinTest, CleansStaleDownloads) {
diff --git a/components/update_client/update_client.cc b/components/update_client/update_client.cc
index a58f918..b19f9c0a 100644
--- a/components/update_client/update_client.cc
+++ b/components/update_client/update_client.cc
@@ -235,16 +235,6 @@
 
   is_stopped_ = true;
 
-  // In the current implementation it is sufficient to cancel the pending
-  // tasks only. The tasks that are run by the update engine will stop
-  // making progress naturally, as the main task runner stops running task
-  // actions. Upon the browser shutdown, the resources employed by the active
-  // tasks will leak, as the operating system kills the thread associated with
-  // the update engine task runner. Further refactoring may be needed in this
-  // area, to cancel the running tasks by canceling the current action update.
-  // This behavior would be expected, correct, and result in no resource leaks
-  // in all cases, in shutdown or not.
-  //
   // Cancel the pending tasks. These tasks are safe to cancel and delete since
   // they have not picked up by the update engine, and not shared with any
   // task runner yet.
@@ -253,6 +243,14 @@
     task_queue_.pop_front();
     task->Cancel();
   }
+
+  // Also cancel active tasks to trigger downloader cleanup. Otherwise, upon the
+  // browser shutdown, the resources employed by the active tasks will leak, as
+  // the operating system kills the thread associated with the update engine
+  // task runner.
+  for (auto& task : tasks_) {
+    task->Cancel();
+  }
 }
 
 void UpdateClientImpl::SendPing(const CrxComponent& crx_component,
diff --git a/components/update_client/update_client_unittest.cc b/components/update_client/update_client_unittest.cc
index 0d0d6e8..76bc84a 100644
--- a/components/update_client/update_client_unittest.cc
+++ b/components/update_client/update_client_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/check_deref.h"
 #include "base/containers/flat_map.h"
 #include "base/containers/to_vector.h"
+#include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
@@ -5773,15 +5774,6 @@
 
   MockObserver observer(update_client);
   {
-    InSequence seq;
-    EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) {
-                  return item.id == "jebgalgnebhfojomionfpkfelancnnkf" &&
-                         item.state == ComponentState::kChecking;
-                })));
-    EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) {
-                  return item.id == "jebgalgnebhfojomionfpkfelancnnkf" &&
-                         item.state == ComponentState::kUpToDate;
-                })));
   }
 
   std::vector<CrxUpdateItem> items;
@@ -5790,8 +5782,7 @@
       .WillRepeatedly(
           [&items](const CrxUpdateItem& item) { items.push_back(item); });
 
-  // Do two `CheckForUpdate` calls, expect the second call to be cancelled,
-  // because `Stop` cancels the queued up subsequent call.
+  // Do two `CheckForUpdate` calls, expect both calls to be cancelled.
   base::RepeatingClosure barrier_quit_closure =
       BarrierClosure(2, runloop_.QuitClosure());
   const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
@@ -5799,20 +5790,16 @@
       id, base::BindOnce(&DataCallbackMock::Callback),
       base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver),
       /*is_foreground=*/true,
-      ExpectErrorThenQuit(barrier_quit_closure, Error::NONE));
+      ExpectErrorThenQuit(barrier_quit_closure, Error::UPDATE_CANCELED));
   update_client->CheckForUpdate(
       id, base::BindOnce(&DataCallbackMock::Callback),
       base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver),
       /*is_foreground=*/true,
       ExpectErrorThenQuit(barrier_quit_closure, Error::UPDATE_CANCELED));
-  update_client->Stop();
   EXPECT_TRUE(update_client->IsUpdating(id));
+  update_client->Stop();
   runloop_.Run();
-  EXPECT_EQ(items.size(), 2u);
-  EXPECT_EQ(items[0].state, ComponentState::kChecking);
-  EXPECT_EQ(items[0].id, "jebgalgnebhfojomionfpkfelancnnkf");
-  EXPECT_EQ(items[1].state, ComponentState::kUpToDate);
-  EXPECT_EQ(items[1].id, "jebgalgnebhfojomionfpkfelancnnkf");
+  EXPECT_TRUE(items.empty());
 }
 
 TEST_F(UpdateClientTest, CheckForUpdate_Errors) {
@@ -6599,4 +6586,173 @@
   EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id);
 }
 
+// Tests cancellation of an active download when `UpdateClient::Stop` is called.
+TEST_F(UpdateClientTest, Install_StopCancelsActiveDownload) {
+  base::FilePath temp_dir;
+#if BUILDFLAG(IS_WIN)
+  ASSERT_TRUE(base::GetSecureTempDirectory(&temp_dir));
+#else   // BUILDFLAG(IS_WIN)
+  ASSERT_TRUE(base::GetTempDir(&temp_dir));
+#endif  // BUILDFLAG(IS_WIN)
+
+  base::FileEnumerator(
+      temp_dir, /*recursive=*/false, base::FileEnumerator::DIRECTORIES,
+      base::StrCat({UTF8ToStringType(config()->GetProdId()),
+                    FILE_PATH_LITERAL("_chrome_url_fetcher_")}))
+      .ForEach([](const base::FilePath& path) {
+        ASSERT_TRUE(RetryFileOperation(&base::DeletePathRecursively, path));
+      });
+
+  class DataCallbackMock {
+   public:
+    static void Callback(
+        const std::vector<std::string>& ids,
+        base::OnceCallback<
+            void(const std::vector<std::optional<CrxComponent>>&)> callback) {
+      CrxComponent crx;
+      crx.app_id = "jebgalgnebhfojomionfpkfelancnnkf";
+      crx.name = "test_jebg";
+      crx.pk_hash = base::ToVector(jebg_hash);
+      crx.version = base::Version("0.0");
+      crx.installer = base::MakeRefCounted<TestInstaller>();
+      crx.crx_format_requirement = crx_file::VerifierFormat::CRX3;
+      std::move(callback).Run({crx});
+    }
+  };
+
+  MockUpdateCheckerFactory<
+      MockUpdateCheckerImpl<UpdateCheckerOptionsOneCrxInstall>>
+      mock_update_checker_factory;
+
+  class MockCrxDownloader : public CrxDownloader {
+   public:
+    MockCrxDownloader() = default;
+
+   private:
+    ~MockCrxDownloader() override = default;
+
+    base::OnceClosure DoStartDownload(const GURL& url) override {
+      DownloadMetrics download_metrics;
+      base::FilePath path;
+      Result result;
+      if (url.GetPath() == "/download/jebgalgnebhfojomionfpkfelancnnkf.crx") {
+        download_metrics.url = url;
+        download_metrics.downloader = DownloadMetrics::kNone;
+        download_metrics.error = 0;
+        download_metrics.downloaded_bytes = 1015;
+        download_metrics.total_bytes = 1015;
+        download_metrics.download_time_ms = 1000;
+
+        EXPECT_TRUE(MakeTestFile(
+            GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
+
+        result.error = 0;
+        result.response = path;
+      } else {
+        ADD_FAILURE();
+      }
+
+      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+                                    base::Unretained(this),
+                                    download_metrics.downloaded_bytes,
+                                    download_metrics.total_bytes));
+
+      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+                                    base::Unretained(this), true, result,
+                                    download_metrics));
+
+      EXPECT_CALL(cancel_callback_, Run()).Times(testing::AtLeast(1));
+      return cancel_callback_.Get();
+    }
+
+    base::MockCallback<base::OnceClosure> cancel_callback_;
+  };
+
+  class MockPingManager : public MockPingManagerImpl {
+   public:
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
+
+   protected:
+    ~MockPingManager() override {
+      // Verify the ping data shows that the active task was cancelled.
+      const auto ping_data = MockPingManagerImpl::terminal_ping_data();
+      EXPECT_EQ(1u, ping_data.size());
+      EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+      EXPECT_EQ(base::Version("0.0"), ping_data[0].previous_version);
+      EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+      EXPECT_EQ(ErrorCategory::kService, ping_data[0].error_category);
+      EXPECT_EQ(static_cast<int>(ServiceError::CANCELLED),
+                ping_data[0].error_code);
+    }
+  };
+
+  SetMockCrxDownloader<MockCrxDownloader>();
+  scoped_refptr<UpdateClient> update_client =
+      base::MakeRefCounted<UpdateClientImpl>(
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          mock_update_checker_factory.GetFactory());
+
+  MockObserver observer(update_client);
+  {
+    InSequence seq;
+    EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) {
+                  return item.id == "jebgalgnebhfojomionfpkfelancnnkf" &&
+                         item.state == ComponentState::kChecking;
+                })));
+    EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) {
+                  return item.id == "jebgalgnebhfojomionfpkfelancnnkf" &&
+                         item.state == ComponentState::kCanUpdate;
+                })));
+    EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) {
+                  return item.id == "jebgalgnebhfojomionfpkfelancnnkf" &&
+                         item.state == ComponentState::kDownloading;
+                })))
+        .Times(AtLeast(1))
+        .WillRepeatedly([&update_client] {
+          // Call `Stop` during the download.
+          update_client->Stop();
+        });
+    EXPECT_CALL(observer, OnEvent(Truly([](const CrxUpdateItem& item) {
+                  return item.id == "jebgalgnebhfojomionfpkfelancnnkf" &&
+                         item.state == ComponentState::kUpdateError;
+                })));
+  }
+
+  std::vector<CrxUpdateItem> items;
+  auto receiver = base::MakeRefCounted<MockCrxStateChangeReceiver>();
+  EXPECT_CALL(*receiver, Receive(_))
+      .WillRepeatedly(
+          [&items](const CrxUpdateItem& item) { items.push_back(item); });
+
+  update_client->Install(
+      std::string("jebgalgnebhfojomionfpkfelancnnkf"),
+      base::BindOnce(&DataCallbackMock::Callback),
+      base::BindRepeating(&MockCrxStateChangeReceiver::Receive, receiver),
+      ExpectErrorThenQuit(runloop_, Error::NONE));
+  runloop_.Run();
+
+  EXPECT_EQ(5u, items.size());
+  EXPECT_EQ(ComponentState::kChecking, items[0].state);
+  EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[0].id);
+  EXPECT_EQ(ComponentState::kCanUpdate, items[1].state);
+  EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[1].id);
+  EXPECT_EQ(ComponentState::kDownloading, items[2].state);
+  EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[2].id);
+  EXPECT_EQ(ComponentState::kDownloading, items[3].state);
+  EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[3].id);
+  EXPECT_EQ(ComponentState::kUpdateError, items[4].state);
+  EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", items[4].id);
+
+  base::FileEnumerator(
+      temp_dir, /*recursive=*/false, base::FileEnumerator::DIRECTORIES,
+      base::StrCat({UTF8ToStringType(config()->GetProdId()),
+                    FILE_PATH_LITERAL("_chrome_url_fetcher_")}))
+      .ForEach([](const base::FilePath& path) {
+        ADD_FAILURE() << "Unexpected left over directory found: " << path;
+      });
+}
+
 }  // namespace update_client
diff --git a/components/update_client/utils.cc b/components/update_client/utils.cc
index f49ea3f..666474d 100644
--- a/components/update_client/utils.cc
+++ b/components/update_client/utils.cc
@@ -19,6 +19,7 @@
 #include "base/containers/heap_array.h"
 #include "base/containers/span.h"
 #include "base/files/file.h"
+#include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/memory_mapped_file.h"
@@ -237,4 +238,22 @@
 }
 #endif  // BUILDFLAG(IS_WIN)
 
+void CleanupDirectoriesOlderThan(const base::FilePath& dir,
+                                 const base::FilePath::StringType& matcher,
+                                 base::TimeDelta older_than) {
+  // Enumerate the directories matching `matcher`.
+  base::FileEnumerator(dir,
+                       /*recursive=*/false, base::FileEnumerator::DIRECTORIES,
+                       matcher)
+      .ForEach([&](const base::FilePath& dir) {
+        base::File::Info info;
+
+        // Delete the directories older than `older_than`.
+        if (base::GetFileInfo(dir, &info) &&
+            ((info.creation_time + older_than) < base::Time::Now())) {
+          RetryFileOperation(&base::DeletePathRecursively, dir);
+        }
+      });
+}
+
 }  // namespace update_client
diff --git a/components/update_client/utils.h b/components/update_client/utils.h
index 7237742..9f9534579 100644
--- a/components/update_client/utils.h
+++ b/components/update_client/utils.h
@@ -126,6 +126,12 @@
 }
 #endif  // BUILDFLAG(IS_WIN)
 
+// Perform a best-effort cleanup up of directories under `dir` that match
+// `matcher` and are `older_than`.
+void CleanupDirectoriesOlderThan(const base::FilePath& dir,
+                                 const base::FilePath::StringType& matcher,
+                                 base::TimeDelta older_than);
+
 }  // namespace update_client
 
 #endif  // COMPONENTS_UPDATE_CLIENT_UTILS_H_
diff --git a/components/update_client/utils_unittest.cc b/components/update_client/utils_unittest.cc
index 0ea25984..8222dd3f 100644
--- a/components/update_client/utils_unittest.cc
+++ b/components/update_client/utils_unittest.cc
@@ -24,7 +24,11 @@
 #include "url/gurl.h"
 
 #if BUILDFLAG(IS_WIN)
+#include <windows.h>
+
 #include <shlobj.h>
+
+#include "base/win/windows_types.h"
 #endif  // BUILDFLAG(IS_WIN)
 
 #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
@@ -33,6 +37,16 @@
 
 namespace update_client {
 
+namespace {
+constexpr base::FilePath::CharType kTestDirPrefix[] =
+    FILE_PATH_LITERAL("_utils_unittest_");
+constexpr base::FilePath::CharType kTestDirMatcher[] =
+    FILE_PATH_LITERAL("_utils_unittest_*");
+constexpr base::FilePath::CharType kTestDownloadFilename[] =
+    FILE_PATH_LITERAL("test_file.txt");
+constexpr char kTestDownloadContent[] = "Hello, World!";
+}  // namespace
+
 TEST(UpdateClientUtils, VerifyFileHash256) {
   EXPECT_TRUE(VerifyFileHash256(
       GetTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"),
@@ -229,6 +243,52 @@
   ASSERT_TRUE(RetryFileOperation(&base::DeletePathRecursively, tempdir));
 }
 
+#if BUILDFLAG(IS_WIN)
+TEST(UpdateClientUtils, CleanupDirectoriesOlderThan) {
+  base::FilePath download_dir_path;
+  ASSERT_TRUE(base::CreateNewTempDirectory(kTestDirPrefix, &download_dir_path));
+  ASSERT_TRUE(base::WriteFile(download_dir_path.Append(kTestDownloadFilename),
+                              kTestDownloadContent));
+
+  // Manipulate the creation time of the directory to be older than 3 days.
+  FILETIME creation_filetime =
+      (base::Time::NowFromSystemTime() - base::Days(5)).ToFileTime();
+  base::File download_dir(download_dir_path,
+                          base::File::FLAG_OPEN |
+                              base::File::FLAG_WIN_BACKUP_SEMANTICS |
+                              base::File::FLAG_WRITE_ATTRIBUTES);
+  ASSERT_TRUE(download_dir.IsValid());
+  ASSERT_TRUE(::SetFileTime(download_dir.GetPlatformFile(), &creation_filetime,
+                            NULL, NULL));
+  download_dir.Close();
+
+  CleanupDirectoriesOlderThan(download_dir_path.DirName(), kTestDirMatcher,
+                              base::Days(3));
+
+  EXPECT_FALSE(base::DirectoryExists(download_dir_path))
+      << "download_dir_path: " << download_dir_path;
+}
+#endif  // BUILDFLAG(IS_WIN)
+
+TEST(UpdateClientUtils, RetainsRecentDownloads) {
+  base::FilePath download_dir_path;
+  ASSERT_TRUE(base::CreateNewTempDirectory(kTestDirPrefix, &download_dir_path));
+  ASSERT_TRUE(base::WriteFile(download_dir_path.Append(kTestDownloadFilename),
+                              kTestDownloadContent));
+
+  base::FilePath temp_dir;
+#if BUILDFLAG(IS_WIN)
+  ASSERT_TRUE(base::GetSecureTempDirectory(&temp_dir));
+#else   // BUILDFLAG(IS_WIN)
+  ASSERT_TRUE(base::GetTempDir(&temp_dir));
+#endif  // BUILDFLAG(IS_WIN)
+  CleanupDirectoriesOlderThan(temp_dir, kTestDirMatcher, base::Days(3));
+
+  EXPECT_TRUE(base::DirectoryExists(download_dir_path));
+  ASSERT_TRUE(
+      RetryFileOperation(&base::DeletePathRecursively, download_dir_path));
+}
+
 struct UpdateClientUtilsUTF8StringTypeTestCase {
   const base::FilePath::StringType stringtype;
   const std::string utf8;
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index f523d47d..2e11149 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -2136,13 +2136,13 @@
   if (NeedsUrlLoader() && common_params_->url.SchemeIsHTTPOrHTTPS()) {
     if (GetContentClient()->browser()->ShouldPreconnectNavigation(
             frame_tree_node_->current_frame_host()) &&
-        IsAllowedByConnectionAllowlist()) {
+        IsAllowedByConnectionAllowlist(/*is_redirect=*/false)) {
       auto* storage_partition =
           frame_tree_node_->current_frame_host()->GetStoragePartition();
 
       // Initiator frame's `network_restriction_id` is not passed because the
       // preconnection has already been checked against the connection-allowlist
-      // by the `IsAllowedByConnectionAllowlist()` call above.
+      // by the `IsAllowedByConnectionAllowlist(false)` call above.
       storage_partition->GetNetworkContext()->PreconnectSockets(
           1, common_params_->url, network::mojom::CredentialsMode::kInclude,
           GetIsolationInfo().network_anonymization_key(),
@@ -2889,7 +2889,7 @@
   }
 
   // Connection Allowlist: check whether navigation to the url is allowed.
-  if (!IsAllowedByConnectionAllowlist()) {
+  if (!IsAllowedByConnectionAllowlist(/*is_redirect=*/false)) {
     // Create a navigation handle so that the correct error code can be set on
     // it by OnRequestFailedInternal().
     StartNavigation();
@@ -3550,6 +3550,19 @@
               perfetto::Flow::FromPointer(this));
   ScopedCrashKeys crash_keys(*this);
 
+  if (!was_redirected_ &&
+      !IsAllowedByConnectionAllowlist(/*is_redirect=*/true)) {
+    auto completion_status =
+        network::URLLoaderCompletionStatus(net::ERR_UNSAFE_REDIRECT);
+    error_navigation_trigger_ = ErrorNavigationTrigger::kRedirectNotAllowed;
+    OnRequestFailedInternal(completion_status, false /* skip_throttles */,
+                            std::nullopt /* error_page_content */,
+                            false /* collapse_frame */);
+    // DO NOT ADD CODE after this. The previous call to OnRequestFailedInternal
+    // has destroyed the NavigationRequest.
+    return;
+  }
+
   // Sanity check - this can only be set at commit time.
   DCHECK(!auth_challenge_info_);
 
@@ -7326,13 +7339,15 @@
   SetExpectedProcess(post_redirect_process);
 }
 
-bool NavigationRequest::IsAllowedByConnectionAllowlist() {
+bool NavigationRequest::IsAllowedByConnectionAllowlist(bool is_redirect) {
   if (!base::FeatureList::IsEnabled(network::features::kConnectionAllowlists)) {
     return true;
   }
 
-  // Determine the PolicyContainerPolicies to use based on navigation type.
-  const PolicyContainerPolicies* policies = nullptr;
+  if (is_redirect && connection_allowlists_blocks_redirect_) {
+    // TODO(crbug.com/447954811): Implement reporting.
+    return false;
+  }
 
   // If it is renderer-initiated, initiator_frame_token_ will be set and
   // connection allowlist should be checked unless it is a same-document
@@ -7377,6 +7392,9 @@
           navigation_state->policy_container_host();
     }
   }
+
+  // Determine the PolicyContainerPolicies to use based on navigation type.
+  const PolicyContainerPolicies* policies = nullptr;
   if (initiator_policy_container_host) {
     policies = &initiator_policy_container_host->policies();
   }
@@ -7385,8 +7403,18 @@
     return true;
   }
 
-  return network::ConnectionAllowlistMatchesUrl(
-      policies->connection_allowlists.enforced.value(), common_params_->url);
+  if (network::ConnectionAllowlistMatchesUrl(
+          policies->connection_allowlists.enforced.value(),
+          common_params_->url)) {
+    // Default-block any server-side redirects.
+    // TODO(crbug.com/447954811): Implement allowing server-side redirects
+    // based on an attribute. Consider not having a bool on the
+    // NavigationRequest as part of that change.
+    connection_allowlists_blocks_redirect_ = true;
+    return true;
+  }
+
+  return false;
 }
 
 bool NavigationRequest::IsAllowedByCSPDirective(
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index 6711f66..523395ed 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -1969,8 +1969,12 @@
   void CommitPageActivation();
 
   // Checks whether this navigation is allowed based on the connection
-  // allowlist header, if present.
-  bool IsAllowedByConnectionAllowlist();
+  // allowlist header, if present. This method can have two side effects:
+  // - If a CA is configured to send reports and the request violates the CA,
+  //   a report will be sent.
+  // - If CA is checked, the navigation request's
+  //  connection_allowlists_blocks_redirect_ will be set accordingly.
+  bool IsAllowedByConnectionAllowlist(bool is_redirect);
 
   // Checks if the specified CSP context's relevant CSP directive
   // allows the navigation. This is called to perform the frame-src check.
@@ -3441,6 +3445,11 @@
   // request.
   bool did_encounter_cross_origin_redirect_ = false;
 
+  // This field is checked to see if server-side redirects should be blocked.
+  // It is only used if Connection allowlists were consulted when this
+  // navigation started.
+  bool connection_allowlists_blocks_redirect_ = false;
+
   // A scoped reference on the ViewTransition resources generated for this
   // navigation. This is set after we received the cached results from the old
   // Document's renderer. If the navigation commits, the resources are
diff --git a/content/browser/webid/request_service.cc b/content/browser/webid/request_service.cc
index 27deca42..a512502 100644
--- a/content/browser/webid/request_service.cc
+++ b/content/browser/webid/request_service.cc
@@ -225,23 +225,18 @@
 void RequestService::BindReceiver(
     mojo::PendingReceiver<blink::mojom::FederatedAuthRequest>
         pending_receiver) {
-  if (receiver_.is_bound()) {
-    // This should only happen with a compromised renderer.
-    // TODO(crbug.com/40810039): Call ReportBadMessage.
-    return;
-  }
-  receiver_.Bind(std::move(pending_receiver));
+  receivers_.Add(this, std::move(pending_receiver));
 }
 
 void RequestService::ReportBadMessage(const char* message) {
-  receiver_.ReportBadMessage(message);
+  receivers_.ReportBadMessage(message);
 }
 
 void RequestService::ResetAndDeleteThisForTesting() {
-  // Resetting the receiver_ before we destruct the objects means that
+  // Resetting the receivers_ before we destruct the objects means that
   // callbacks won't be called. This matches DocumentService::ResetAndDeleteThis
   // and is what our tests expect.
-  receiver_.reset();
+  receivers_.Clear();
   DeleteForCurrentDocument(&render_frame_host());
 }
 
diff --git a/content/browser/webid/request_service.h b/content/browser/webid/request_service.h
index 31d68cd..2a140aac 100644
--- a/content/browser/webid/request_service.h
+++ b/content/browser/webid/request_service.h
@@ -682,7 +682,7 @@
   // Can be set to true in tests.
   bool force_allow_redirect_to_for_testing_{false};
 
-  mojo::Receiver<blink::mojom::FederatedAuthRequest> receiver_{this};
+  mojo::ReceiverSet<blink::mojom::FederatedAuthRequest> receivers_;
 
   base::WeakPtrFactory<RequestService> weak_ptr_factory_{this};
 };
diff --git a/extensions/browser/api/BUILD.gn b/extensions/browser/api/BUILD.gn
index ad3f563..a1caf57 100644
--- a/extensions/browser/api/BUILD.gn
+++ b/extensions/browser/api/BUILD.gn
@@ -31,6 +31,7 @@
   # TODO(https://crbug.com/356905053): Continue moving more APIs here.
   public_deps = [
     "//extensions/browser/api/alarms",
+    "//extensions/browser/api/content_settings",
     "//extensions/browser/api/declarative_net_request",
     "//extensions/browser/api/dns",
     "//extensions/browser/api/file_handlers",
@@ -64,7 +65,6 @@
       "//extensions/browser/api/bluetooth",
       "//extensions/browser/api/bluetooth_low_energy",
       "//extensions/browser/api/bluetooth_socket",
-      "//extensions/browser/api/content_settings",
       "//extensions/browser/api/feedback_private",
       "//extensions/browser/api/file_system",
       "//extensions/browser/api/mime_handler_private",
diff --git a/extensions/browser/api/api_browser_context_keyed_service_factories.cc b/extensions/browser/api/api_browser_context_keyed_service_factories.cc
index bd241c0..52117749 100644
--- a/extensions/browser/api/api_browser_context_keyed_service_factories.cc
+++ b/extensions/browser/api/api_browser_context_keyed_service_factories.cc
@@ -7,6 +7,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "extensions/browser/api/alarms/alarm_manager.h"
+#include "extensions/browser/api/content_settings/content_settings_service.h"
 #include "extensions/browser/api/declarative/rules_registry_service.h"
 #include "extensions/browser/api/declarative_net_request/rules_monitor_service.h"
 #include "extensions/browser/api/idle/idle_manager_factory.h"
@@ -35,7 +36,6 @@
 #include "extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_notify_session.h"
 #include "extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h"
 #include "extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h"
-#include "extensions/browser/api/content_settings/content_settings_service.h"  // nogncheck
 #include "extensions/browser/api/feedback_private/feedback_private_api.h"
 #include "extensions/browser/api/hid/hid_connection_resource.h"
 #include "extensions/browser/api/hid/hid_device_manager.h"
@@ -72,6 +72,7 @@
 
 void EnsureApiBrowserContextKeyedServiceFactoriesBuilt() {
   AlarmManager::GetFactoryInstance();
+  ContentSettingsService::GetFactoryInstance();
   declarative_net_request::RulesMonitorService::GetFactoryInstance();
   IdleManagerFactory::GetInstance();
   ManagementAPI::GetFactoryInstance();
@@ -124,7 +125,6 @@
 #if BUILDFLAG(IS_CHROMEOS)
   ClipboardAPI::GetFactoryInstance();
 #endif
-  ContentSettingsService::GetFactoryInstance();
   FeedbackPrivateAPI::GetFactoryInstance();
   HidDeviceManager::GetFactoryInstance();
 #if BUILDFLAG(IS_CHROMEOS)
diff --git a/extensions/common/api/content_scripts.webidl b/extensions/common/api/content_scripts.webidl
new file mode 100644
index 0000000..b325e022
--- /dev/null
+++ b/extensions/common/api/content_scripts.webidl
@@ -0,0 +1,80 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[ExternalExtensionType="extensionTypes.RunAt"]
+typedef object ExtensionTypesRunAt;
+
+[ExternalExtensionType="extensionTypes.ExecutionWorld"]
+typedef object ExtensionTypesExecutionWorld;
+
+// Describes a content script to be injected into a web page.
+dictionary ContentScript {
+  // Specifies which pages this content script will be injected into. See
+  // <a href="develop/concepts/match-patterns">Match Patterns</a> for more
+  // details on the syntax of these strings.
+  required sequence<DOMString> matches;
+
+  // Excludes pages that this content script would otherwise be injected into.
+  // See <a href="develop/concepts/match-patterns">Match Patterns</a> for more
+  // details on the syntax of these strings.
+  sequence<DOMString> exclude_matches;
+
+  // The list of CSS files to be injected into matching pages. These are
+  // injected in the order they appear in this array, before any DOM is
+  // constructed or displayed for the page.
+  sequence<DOMString> css;
+
+  // The list of JavaScript files to be injected into matching pages. These
+  // are injected in the order they appear in this array.
+  sequence<DOMString> js;
+
+  // If specified true, it will inject into all frames, even if the frame is
+  // not the top-most frame in the tab. Each frame is checked independently
+  // for URL requirements; it will not inject into child frames if the URL
+  // requirements are not met. Defaults to false, meaning that only the top
+  // frame is matched.
+  boolean all_frames;
+
+  // Whether the script should inject into any frames where the URL belongs to
+  // a scheme that would never match a specified Match Pattern, including
+  // about:, data:, blob:, and filesystem: schemes. In these cases, in order
+  // to determine if the script should inject, the origin of the URL is
+  // checked. If the origin is `null` (as is the case for data: URLs), then
+  // the "initiator" or "creator" origin is used (i.e., the origin of the
+  // frame that created or navigated this frame). Note that this may not
+  // be the parent frame, if the frame was navigated by another frame in the
+  // document hierarchy.
+  boolean match_origin_as_fallback;
+
+  // Whether the script should inject into an about:blank frame where the
+  // parent or opener frame matches one of the patterns declared in matches.
+  // Defaults to false.
+  boolean match_about_blank;
+
+  // Applied after matches to include only those URLs that also match this
+  // glob. Intended to emulate the
+  // <a href="http://wiki.greasespot.net/Metadata_Block#.40include">@include
+  // </a> Greasemonkey keyword.
+  sequence<DOMString> include_globs;
+
+  // Applied after matches to exclude URLs that match this glob. Intended to
+  // emulate the
+  // <a href="https://wiki.greasespot.net/Metadata_Block#.40exclude">@exclude
+  // </a> Greasemonkey keyword.
+  sequence<DOMString> exclude_globs;
+
+  // Specifies when JavaScript files are injected into the web page. The
+  // preferred and default value is <code>document_idle</code>.
+  ExtensionTypesRunAt run_at;
+
+  // The JavaScript "world" to run the script in. Defaults to
+  // <code>ISOLATED</code>. Only available in Manifest V3 extensions.
+  ExtensionTypesExecutionWorld world;
+};
+
+// Stub namespace for the "content_scripts" manifest key.
+[generate_error_messages, Namespace=contentScripts]
+partial dictionary ExtensionManifest {
+  sequence<ContentScript> content_scripts;
+};
diff --git a/extensions/common/api/schema.gni b/extensions/common/api/schema.gni
index ff23310..412efe9 100644
--- a/extensions/common/api/schema.gni
+++ b/extensions/common/api/schema.gni
@@ -40,7 +40,7 @@
 # parsing.
 extensions_types_only_schema_files_ = [
   "chrome_url_overrides.webidl",
-  "content_scripts.idl",
+  "content_scripts.webidl",
   "cross_origin_isolation.webidl",
   "extensions_manifest_types.json",
   "incognito.json",
diff --git a/ios/chrome/browser/ntp/ui_bundled/incognito/BUILD.gn b/ios/chrome/browser/ntp/ui_bundled/incognito/BUILD.gn
index 3b3e9be..a66805d 100644
--- a/ios/chrome/browser/ntp/ui_bundled/incognito/BUILD.gn
+++ b/ios/chrome/browser/ntp/ui_bundled/incognito/BUILD.gn
@@ -20,7 +20,6 @@
     "//ios/chrome/browser/shared/ui/symbols",
     "//ios/chrome/browser/shared/ui/util",
     "//ios/chrome/browser/toolbar/legacy/ui_bundled/public:constants",
-    "//ios/chrome/browser/url_loading/model",
     "//ios/chrome/common:string_util",
     "//ios/chrome/common/ui/colors",
     "//ios/chrome/common/ui/util",
diff --git a/ios/chrome/browser/ntp/ui_bundled/incognito/incognito_view_controller.h b/ios/chrome/browser/ntp/ui_bundled/incognito/incognito_view_controller.h
index 22dc1a9..fa2f56e 100644
--- a/ios/chrome/browser/ntp/ui_bundled/incognito/incognito_view_controller.h
+++ b/ios/chrome/browser/ntp/ui_bundled/incognito/incognito_view_controller.h
@@ -7,16 +7,12 @@
 
 #import <UIKit/UIKit.h>
 
-class UrlLoadingBrowserAgent;
+@protocol NewTabPageURLLoaderDelegate;
 
 @interface IncognitoViewController : UIViewController
 
-// Init with the given loader object. `loader` may be nil, but isn't
-// retained so it must outlive this controller.
-// TODO(crbug.com/40228520): View controllers should not have access to
-// model-layer objects. Create a mediator to connect model-layer class
-// `UrlLoadingBrowserAgent` to the view controller.
-- (instancetype)initWithUrlLoader:(UrlLoadingBrowserAgent*)URLLoader;
+// Delegate to load URLs in the current tab.
+@property(nonatomic, weak) id<NewTabPageURLLoaderDelegate> URLLoaderDelegate;
 
 @end
 
diff --git a/ios/chrome/browser/ntp/ui_bundled/incognito/incognito_view_controller.mm b/ios/chrome/browser/ntp/ui_bundled/incognito/incognito_view_controller.mm
index 380e99a7..b60af2f 100644
--- a/ios/chrome/browser/ntp/ui_bundled/incognito/incognito_view_controller.mm
+++ b/ios/chrome/browser/ntp/ui_bundled/incognito/incognito_view_controller.mm
@@ -4,42 +4,25 @@
 
 #import "ios/chrome/browser/ntp/ui_bundled/incognito/incognito_view_controller.h"
 
-#import "base/memory/raw_ptr.h"
 #import "ios/chrome/browser/ntp/ui_bundled/incognito/incognito_view.h"
 #import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_constants.h"
 #import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_url_loader_delegate.h"
-#import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h"
-#import "ios/chrome/browser/url_loading/model/url_loading_params.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 
-@interface IncognitoViewController () <NewTabPageURLLoaderDelegate>
+@interface IncognitoViewController ()
 
 // The scrollview containing the actual views.
 @property(nonatomic, strong) UIScrollView* incognitoView;
 
 @end
 
-@implementation IncognitoViewController {
-  // The UrlLoadingService associated with this view.
-  // TODO(crbug.com/40228520): View controllers should not have access to
-  // model-layer objects. Create a mediator to connect model-layer class
-  // `UrlLoadingBrowserAgent` to the view controller.
-  raw_ptr<UrlLoadingBrowserAgent, DanglingUntriaged> _URLLoader;  // weak
-}
-
-- (instancetype)initWithUrlLoader:(UrlLoadingBrowserAgent*)URLLoader {
-  self = [super init];
-  if (self) {
-    _URLLoader = URLLoader;
-  }
-  return self;
-}
+@implementation IncognitoViewController
 
 - (void)viewDidLoad {
   self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
 
   IncognitoView* view = [[IncognitoView alloc] initWithFrame:self.view.bounds];
-  view.URLLoaderDelegate = self;
+  view.URLLoaderDelegate = self.URLLoaderDelegate;
   self.incognitoView = view;
   self.incognitoView.accessibilityIdentifier = kNTPIncognitoViewIdentifier;
   [self.incognitoView setAutoresizingMask:UIViewAutoresizingFlexibleHeight |
@@ -52,10 +35,4 @@
   [_incognitoView setDelegate:nil];
 }
 
-#pragma mark - NewTabPageURLLoaderDelegate
-
-- (void)loadURLInTab:(const GURL&)URL {
-  _URLLoader->Load(UrlLoadParams::InCurrentTab(URL));
-}
-
 @end
diff --git a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm
index dd8dc034..5186a3ec 100644
--- a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm
+++ b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm
@@ -81,6 +81,7 @@
 #import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_component_factory_protocol.h"
 #import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_constants.h"
 #import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_content_delegate.h"
+#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_url_loader_delegate.h"
 #import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_controller_delegate.h"
 #import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator+Testing.h"
 #import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_delegate.h"
@@ -138,6 +139,7 @@
 #import "ios/chrome/browser/toolbar/legacy/ui_bundled/public/fakebox_focuser.h"
 #import "ios/chrome/browser/toolbar/tab_group/coordinator/tab_group_indicator_coordinator.h"
 #import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h"
+#import "ios/chrome/browser/url_loading/model/url_loading_params.h"
 #import "ios/chrome/browser/web/model/web_navigation_util.h"
 #import "ios/chrome/common/NSString+Chromium.h"
 #import "ios/chrome/common/material_timing.h"
@@ -167,6 +169,7 @@
                                      NewTabPageDelegate,
                                      NewTabPageHeaderCommands,
                                      NewTabPageActionsDelegate,
+                                     NewTabPageURLLoaderDelegate,
                                      OverscrollActionsControllerDelegate,
                                      ProfileStateObserver,
                                      SceneStateObserver,
@@ -342,10 +345,8 @@
   // Configures incognito NTP if user is in incognito mode.
   if (self.isOffTheRecord) {
     DCHECK(!self.incognitoViewController);
-    UrlLoadingBrowserAgent* URLLoader =
-        UrlLoadingBrowserAgent::FromBrowser(self.browser);
-    self.incognitoViewController =
-        [[IncognitoViewController alloc] initWithUrlLoader:URLLoader];
+    self.incognitoViewController = [[IncognitoViewController alloc] init];
+    self.incognitoViewController.URLLoaderDelegate = self;
     self.started = YES;
     return;
   }
@@ -1279,6 +1280,13 @@
   return self.authService->SigninEnabled();
 }
 
+#pragma mark - NewTabPageURLLoaderDelegate
+
+- (void)loadURLInTab:(const GURL&)URL {
+  UrlLoadingBrowserAgent::FromBrowser(self.browser)
+      ->Load(UrlLoadParams::InCurrentTab(URL));
+}
+
 #pragma mark - NewTabPageActionsDelegate
 
 - (void)recentTabTileOpenedAtIndex:(NSUInteger)index {
diff --git a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
index f8136709..8d91c72 100644
--- a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-68701c367aa9c320e7d81bd4e09acc57791faba2
\ No newline at end of file
+ff73c142ad02fd3d3e499fbad34afca4c8dda1a9
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
index 0e49fd5..e3668de 100644
--- a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-9e25cb428caffc24e780a0bd10171625e2f5a2e2
\ No newline at end of file
+be2f17f2274057fed8a2f7a0ef742a5c4ff0e16f
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
index a0669e34..65ae807 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-46f584355ef136803b0a79ed179424374a8864a3
\ No newline at end of file
+3fab3fd65e3305950e1c6ee4c3b945e401b4728b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
index 56fbdab0..fa428b95 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-527a7ddc5b411293fc2616f4f9f4e93b2c8c6305
\ No newline at end of file
+e18be4059bf75a990670e7f1f1c3e763bad9dd1e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
index ef9df33b..5650149e 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-65e6b9a378e530dcaa6007c7262f23e9c7f2ded5
\ No newline at end of file
+6b19308c728ed7e4b750c31e28109cf726601b2a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
index a444666..7222d56 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-8289701351e488778e30461aefcbd7efccf028d8
\ No newline at end of file
+7a6a556468286fd44cfafd5e9dd2884c411b64f9
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
index 288e2f2..3ee098e 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-f92626cd7c815774f1c0fdc3d9f41b4969fac617
\ No newline at end of file
+ee91cbb3974338dc518cd74d42320904d0a39199
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
index 1da0948..40dcf78 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-1b342133f12e4d73539e9aef65b02840bc67842d
\ No newline at end of file
+937030f1a64ede920d669d7b6085a98bdc088c21
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
index 6e8fddd..b35a3fd 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-4845b05f59e543329814f86a9b360e7dc530e1e9
\ No newline at end of file
+e51f23200cff6267d28c80381edb13a5952a6638
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 13c52e2..8d65b5ff 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-7cd3c3c7ed9a8c60aeae8062de8e36a075acdd3a
\ No newline at end of file
+cf6d86339d042e9f6ae38ad3bd5a83959441b8ec
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index 79ad84d..3744a014 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-c510940a763880cf27d018e53453cc4d87b9c1d8
\ No newline at end of file
+3d1c4d8fcfd6dc2a6c7ade79f66acee00c2a7644
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
index 30cb02a..0e9c8ac 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-5ca84f66f945e9d154c7eff4fc34f048e360da24
\ No newline at end of file
+f4901ce56ec35ae4fd61a54ccf269d9ca58dddba
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index af3908da..dcf4809 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-3e94ac42450ebd2153cbc711478fafd73472d85c
\ No newline at end of file
+d0cf978dfde173a0b959f5bd06b15390f74cd1e8
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 2dbffb7..77cf252 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-cab9a4189a1179699cbbd9bb2114b16efef08971
\ No newline at end of file
+18127472fef224904204d9ac77e13393be3fa8a1
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index 18be966..e75fbbe 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-638e9e919f4d8ec8797b2aaa60bfe03dcae3490d
\ No newline at end of file
+f2bad34fffe979b65023b9e407c4f9ee801cd418
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
index 3da7453..97c52d2 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-7b224a217461772817290f684321ff5f83858eb3
\ No newline at end of file
+b19eacee68fd66a5ae93d22b7c003144c899f9c1
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index b26e970e..7d99e55 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-5b7c3f7977cc5ff61754182f9eaa358f78188ac1
\ No newline at end of file
+a67d32e123afdb3919b76643a05ef418f2e907af
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 4439415..cf081792 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-1655c86126c8b3511259ce445d969eeb07340fc2
\ No newline at end of file
+978ffdf7df764c5d96d7b5bf30594970993e4a8a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
index 905a824..2aee7d63 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-89af12397b5485913c0581123969c217f6551802
\ No newline at end of file
+2c42885111ff287b51cbaddd521d288a209e26ce
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
index 713043c..6564010 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-14b50c45e7caed7acaf0ffe83e3015de6533711e
\ No newline at end of file
+69019f1d27be648e2dd61e0067f5741cbf043c5b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index a6a9a164..e4f9371 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-90d1ffa1ff30a8e034286ba9663ef5a65bcd9493
\ No newline at end of file
+bae26756df481255e26cbf764ef3191e50e027ab
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
index 3b88356..b7de425 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-5013fc7c435afd6f0aac4368352bded387eccc56
\ No newline at end of file
+91e7e94e5b1860b51290b209658002c56abf06eb
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index 35e55485..3086585 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-f7382912f988440a469a6bb6a377a270078815d8
\ No newline at end of file
+fbfafc4db346cd436c35f193b15d3b4db4fb9225
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 4a94fe7..e3fefbc 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-8658494d2f61216126c9cb105927545544762ecc
\ No newline at end of file
+d284dbab60e07544c3e68e26240eac27b8a26915
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index 2471110a..f35d02d 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-96eccf6889832a26cdcb74a3f59f3f107c460c4f
\ No newline at end of file
+a1f7f584f65cabfe96c905b89d87ac267b44b9a4
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
index a9e7969..179c7f5 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-debc34dc3416a785eacf029374ab23e2d9965a74
\ No newline at end of file
+613f8e20bfa03e3bedceff00488a4a1b7ddfb350
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 7679cc2..5c44d56c81 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-4d6f383e5d4c1dad8817aca1a1d90d7690e1a652
\ No newline at end of file
+80c93cb8b1052db15cce13f7d8eded5ec55b7f82
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 0d8c851..a90da16 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-fbe523f06e30096f97f554cebc361e8dc2c3c4fe
\ No newline at end of file
+0191c04ab31fd65b1e5fb014dec669d8bf09882a
\ No newline at end of file
diff --git a/ios/web/js_features/crash_keys/resources/crash_keys.ts b/ios/web/js_features/crash_keys/resources/crash_keys.ts
index b21e40dd..b962eb20 100644
--- a/ios/web/js_features/crash_keys/resources/crash_keys.ts
+++ b/ios/web/js_features/crash_keys/resources/crash_keys.ts
@@ -27,10 +27,11 @@
 }
 
 /**
- * Returns a copy of the current crash keys.
+ * Returns the internal map of crash keys as an object so it can be passed
+ * across the JS->native bridge.
  */
-export function getCrashKeys(): Map<string, string> {
-  return new Map(getInternalMap());
+export function getCrashKeys(): Object {
+  return Object.fromEntries(getInternalMap());
 }
 
 /**
diff --git a/ios/web/js_features/crash_keys/resources/crash_keys_test.ts b/ios/web/js_features/crash_keys/resources/crash_keys_test.ts
index 71e4f1e6..4e8bf356 100644
--- a/ios/web/js_features/crash_keys/resources/crash_keys_test.ts
+++ b/ios/web/js_features/crash_keys/resources/crash_keys_test.ts
@@ -13,8 +13,7 @@
 
 gCrWeb.registerApi(crash_keys_tests);
 
-crash_keys_tests.addFunction(
-    'getCrashKeys', () => Object.fromEntries(getCrashKeys()));
+crash_keys_tests.addFunction('getCrashKeys', getCrashKeys);
 crash_keys_tests.addFunction('setCrashKey', setCrashKey);
 crash_keys_tests.addFunction('clearCrashKey', clearCrashKey);
 crash_keys_tests.addFunction('clearAllCrashKeys', clearAllCrashKeys);
diff --git a/ios/web/js_features/window_error/ios_javascript_error_report.h b/ios/web/js_features/window_error/ios_javascript_error_report.h
index 85a8f37d..0b59ddc 100644
--- a/ios/web/js_features/window_error/ios_javascript_error_report.h
+++ b/ios/web/js_features/window_error/ios_javascript_error_report.h
@@ -5,6 +5,7 @@
 #ifndef IOS_WEB_JS_FEATURES_WINDOW_ERROR_IOS_JAVASCRIPT_ERROR_REPORT_H_
 #define IOS_WEB_JS_FEATURES_WINDOW_ERROR_IOS_JAVASCRIPT_ERROR_REPORT_H_
 
+#include <map>
 #include <optional>
 #include <string>
 
@@ -62,6 +63,9 @@
   // This can be important information for debugging since the full URL is not
   // available in `page_url`
   std::optional<std::string> page_url_file_extension;
+
+  // The crash keys set in the JavaScript code before this error occurred.
+  std::optional<std::map<std::string, std::string>> crash_keys;
 };
 
 #endif  // IOS_WEB_JS_FEATURES_WINDOW_ERROR_IOS_JAVASCRIPT_ERROR_REPORT_H_
diff --git a/ios/web/js_features/window_error/script_error_details.h b/ios/web/js_features/window_error/script_error_details.h
index e872fa7..6fc37ce9 100644
--- a/ios/web/js_features/window_error/script_error_details.h
+++ b/ios/web/js_features/window_error/script_error_details.h
@@ -5,6 +5,7 @@
 #ifndef IOS_WEB_JS_FEATURES_WINDOW_ERROR_SCRIPT_ERROR_DETAILS_H_
 #define IOS_WEB_JS_FEATURES_WINDOW_ERROR_SCRIPT_ERROR_DETAILS_H_
 
+#import <map>
 #import <string>
 
 #import "url/gurl.h"
@@ -35,6 +36,9 @@
 
   // Whether or not this error occurred in the main frame.
   bool is_main_frame;
+
+  // The crash keys set in the JavaScript code before this error occurred.
+  std::map<std::string, std::string> crash_keys;
 };
 
 #endif  // IOS_WEB_JS_FEATURES_WINDOW_ERROR_SCRIPT_ERROR_DETAILS_H_
diff --git a/ios/web/js_features/window_error/script_error_message_handler_java_script_feature.mm b/ios/web/js_features/window_error/script_error_message_handler_java_script_feature.mm
index a4c5e60f..849310d 100644
--- a/ios/web/js_features/window_error/script_error_message_handler_java_script_feature.mm
+++ b/ios/web/js_features/window_error/script_error_message_handler_java_script_feature.mm
@@ -23,6 +23,8 @@
 static const char kScriptMessageResponseLineNumberKey[] = "line_number";
 static const char kScriptMessageResponseMessageKey[] = "message";
 static const char kScriptMessageResponseStackKey[] = "stack";
+static const char kScriptMessageResponseCrashKeys[] = "crashKeys";
+
 }  // namespace
 
 namespace web {
@@ -82,6 +84,14 @@
     details.url = script_message.request_url().value();
   }
 
+  const base::DictValue* crash_keys =
+      script_dict->FindDict(kScriptMessageResponseCrashKeys);
+  if (crash_keys) {
+    for (auto ck = crash_keys->begin(); ck != crash_keys->end(); ++ck) {
+      details.crash_keys.insert({ck->first, ck->second.GetString()});
+    }
+  }
+
   if (log_message &&
       base::FeatureList::IsEnabled(features::kLogCrWebJavaScriptErrors)) {
     WebJsErrorReportProcessor::FromBrowserState(web_state->GetBrowserState())
diff --git a/ios/web/js_features/window_error/web_js_error_report_processor.mm b/ios/web/js_features/window_error/web_js_error_report_processor.mm
index dfd92c77..23ef242 100644
--- a/ios/web/js_features/window_error/web_js_error_report_processor.mm
+++ b/ios/web/js_features/window_error/web_js_error_report_processor.mm
@@ -196,6 +196,7 @@
   report.api = details.api;
   report.error_message = details.message;
   report.stack_trace = RedactStack(details.stack);
+  report.crash_keys = details.crash_keys;
   report.page_url = details.url.GetWithEmptyPath().spec();
 
   std::string filename = details.url.ExtractFileName();
@@ -295,6 +296,13 @@
         base::NumberToString(error_report.error_code.value());
   }
 
+  if (error_report.crash_keys) {
+    for (auto ck = error_report.crash_keys->begin();
+         ck != error_report.crash_keys->end(); ++ck) {
+      params[base::StrCat({"JS_", ck->first})] = ck->second;
+    }
+  }
+
   AddExperimentIds(params);
 
   const GURL url(base::StrCat(
diff --git a/ios/web/js_messaging/java_script_feature_util_impl.mm b/ios/web/js_messaging/java_script_feature_util_impl.mm
index 96bd5c77..7b84c9a 100644
--- a/ios/web/js_messaging/java_script_feature_util_impl.mm
+++ b/ios/web/js_messaging/java_script_feature_util_impl.mm
@@ -55,8 +55,13 @@
         //     {error_message}
         //     {api}:{line_number}
         //     {stack}
+        //     {crash_keys}
         //     {url}
         //     {kMainFrameDescription|kIframeDescription}
+        std::string crash_keys_str;
+        for (const auto [key, value] : error_details.crash_keys) {
+          crash_keys_str += "\n " + key + ": " + value;
+        }
         const char* frame_description = error_details.is_main_frame
                                             ? kMainFrameDescription
                                             : kIframeDescription;
@@ -64,7 +69,10 @@
                     << error_details.message << "\n"
                     << error_details.api << ":" << error_details.line_number
                     << "\n  " << error_details.stack << "\n  "
-                    << error_details.url.spec() << "\n  " << frame_description;
+                    << "Crash Keys:"
+                    << (crash_keys_str.empty() ? "None" : crash_keys_str)
+                    << "\n  " << error_details.url.spec() << "\n  "
+                    << frame_description;
         if (base::FeatureList::IsEnabled(features::kAssertOnJavaScriptErrors)) {
           CHECK(false) << "JavaScript error occurred with "
                           "kAssertOnJavaScriptErrors enabled.";
diff --git a/ios/web/public/js_messaging/BUILD.gn b/ios/web/public/js_messaging/BUILD.gn
index fb44e629..de9a930 100644
--- a/ios/web/public/js_messaging/BUILD.gn
+++ b/ios/web/public/js_messaging/BUILD.gn
@@ -55,7 +55,10 @@
 compile_ts("error_reporting") {
   sources = [ "resources/error_reporting.ts" ]
 
-  deps = [ ":util_scripts" ]
+  deps = [
+    ":util_scripts",
+    "//ios/web/js_features/crash_keys:crash_keys_js",
+  ]
 }
 
 # `compile_ts` and `optimize_js` targets are defined separately here instead of
diff --git a/ios/web/public/js_messaging/resources/error_reporting.ts b/ios/web/public/js_messaging/resources/error_reporting.ts
index a7f648f..5073639 100644
--- a/ios/web/public/js_messaging/resources/error_reporting.ts
+++ b/ios/web/public/js_messaging/resources/error_reporting.ts
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {clearAllCrashKeys, getCrashKeys} from '//ios/web/js_features/crash_keys/resources/crash_keys.js';
 import {sendWebKitMessage} from '//ios/web/public/js_messaging/resources/utils.js';
 
 /**
@@ -24,9 +25,14 @@
       }
     }
     if (errorMessage && errorStack) {
-      sendWebKitMessage(
-          'WindowErrorResultHandler',
-          {'message': errorMessage, 'stack': errorStack, 'api': apiName});
+      const crashKeys = getCrashKeys();
+      clearAllCrashKeys();
+      sendWebKitMessage('WindowErrorResultHandler', {
+        'message': errorMessage,
+        'stack': errorStack,
+        'api': apiName,
+        'crashKeys': crashKeys,
+      });
     }
   }
   return undefined;
diff --git a/ios_internal b/ios_internal
index f0f28e1..dc307889 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit f0f28e17f585b2fc159c8f571456d38592803f0d
+Subproject commit dc307889eca75f759981b901f51303bd1703a003
diff --git a/media/gpu/BUILD.gn b/media/gpu/BUILD.gn
index 22c2ed5..64bb10c9 100644
--- a/media/gpu/BUILD.gn
+++ b/media/gpu/BUILD.gn
@@ -772,6 +772,20 @@
   }
 }
 
+if (is_win) {
+  fuzzer_test("media_d3d12_video_encode_delegate_fuzzer") {
+    sources = [ "windows/d3d12_video_encode_delegate_fuzzer.cc" ]
+    deps = [
+      ":gpu",
+      "//base",
+      "//media:test_support",
+      "//media/base/win:test_support",
+      "//testing/gmock",
+      "//testing/gtest",
+    ]
+  }
+}
+
 fuzzer_test("media_vp9_decoder_fuzzer") {
   sources = [ "vp9_decoder_fuzzer.cc" ]
   deps = [
diff --git a/media/gpu/h265_builder.cc b/media/gpu/h265_builder.cc
index 37a2731..ba6415f 100644
--- a/media/gpu/h265_builder.cc
+++ b/media/gpu/h265_builder.cc
@@ -25,8 +25,9 @@
     builder.AppendBits(1,
                        profile_tier_level.general_frame_only_constraint_flag);
     CHECK_LT(profile_tier_level.general_profile_idc, 4);
-    // Check general_profile_compatibility_flag[ 2 ] == 0
-    CHECK(!(profile_tier_level.general_profile_compatibility_flags & 1 << 29));
+    // We are not using the encoder for still image encoding, so the
+    // general_one_picture_only_constraint_flag should always be set to 0. In
+    // that case simply appending 43 zero bits is fine.
     builder.AppendBits(43, 0);  // general_reserved_zero_43bits
     builder.AppendBits(1, 0);   // general_inbld_flag
   }
diff --git a/media/gpu/windows/d3d12_video_encode_av1_delegate.cc b/media/gpu/windows/d3d12_video_encode_av1_delegate.cc
index 708e2a44..9c2d0e3 100644
--- a/media/gpu/windows/d3d12_video_encode_av1_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_av1_delegate.cc
@@ -110,7 +110,7 @@
   return sequence_header;
 }
 
-AV1BitstreamBuilder::FrameHeader FillAV1BuilderFrameHeader(
+std::optional<AV1BitstreamBuilder::FrameHeader> FillAV1BuilderFrameHeader(
     const D3D12VideoEncodeAV1Delegate::PictureControlFlags& picture_ctrl,
     const D3D12_VIDEO_ENCODER_AV1_PICTURE_CONTROL_CODEC_DATA& pic_params,
     const AV1BitstreamBuilder::SequenceHeader& sequence_header) {
@@ -209,7 +209,7 @@
         lr_unit_shift = 0;
         break;
       default:
-        NOTREACHED();
+        return std::nullopt;
     }
     // Check if either restoration_u_tile_size or resotration_v_tile_size is
     // equal to resotration_y_tile_size, if so, lr_uv_shift is 0; otherwise,
@@ -1211,8 +1211,14 @@
 
   DVLOG(4) << PrintPostEncodeValues(post_encode_values);
 
-  auto frame_header = FillAV1BuilderFrameHeader(picture_ctrl_, picture_params_,
-                                                sequence_header_);
+  auto frame_header_or_error = FillAV1BuilderFrameHeader(
+      picture_ctrl_, picture_params_, sequence_header_);
+  if (!frame_header_or_error.has_value()) {
+    return {EncoderStatus::Codes::kEncoderHardwareDriverError,
+            "D3D12VideoEncodeAV1Delegate: invalid restoration tile size."};
+  }
+  AV1BitstreamBuilder::FrameHeader frame_header =
+      std::move(frame_header_or_error).value();
   if (!UpdateFrameHeaderPostEncode(enc_caps_.post_value_flags,
                                    post_encode_values, frame_header)) {
     return {EncoderStatus::Codes::kEncoderHardwareDriverError,
diff --git a/media/gpu/windows/d3d12_video_encode_delegate.cc b/media/gpu/windows/d3d12_video_encode_delegate.cc
index 6c2c1eba..9f82d91 100644
--- a/media/gpu/windows/d3d12_video_encode_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_delegate.cc
@@ -558,6 +558,10 @@
     return std::move(size_or_error).error();
   }
   size_t size = std::move(size_or_error).value();
+  if (size > bitstream_buffer.size()) {
+    return {EncoderStatus::Codes::kEncoderHardwareDriverError,
+            "Encoded bitstream exceeds output buffer size"};
+  }
   D3D12_RANGE written_range{};
   metadata.Commit(&written_range);
   EncoderStatus status =
@@ -583,8 +587,11 @@
                                             DXGI_FORMAT format,
                                             size_t max_num_ref_frames,
                                             bool use_texture_array) {
-  CHECK_GT(max_num_ref_frames, 0u);
-  CHECK_LE(max_num_ref_frames, kMaxDpbSize);
+  if (max_num_ref_frames == 0 || max_num_ref_frames > kMaxDpbSize) {
+    LOG(ERROR) << "Invalid max reference frames number: " << max_num_ref_frames
+               << " (should be between 1 and " << kMaxDpbSize << ")";
+    return false;
+  }
   size_ = max_num_ref_frames;
 
   // We reserve one space in extra for the current frame.
diff --git a/media/gpu/windows/d3d12_video_encode_delegate_fuzzer.cc b/media/gpu/windows/d3d12_video_encode_delegate_fuzzer.cc
new file mode 100644
index 0000000..c24f86a
--- /dev/null
+++ b/media/gpu/windows/d3d12_video_encode_delegate_fuzzer.cc
@@ -0,0 +1,1192 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/gpu/windows/d3d12_video_encode_delegate.h"
+
+#include <fuzzer/FuzzedDataProvider.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <vector>
+
+#include "base/containers/span.h"
+#include "base/functional/bind.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "build/buildflag.h"
+#include "gpu/config/gpu_driver_bug_workarounds.h"
+#include "media/base/bitrate.h"
+#include "media/base/bitstream_buffer.h"
+#include "media/base/video_codecs.h"
+#include "media/base/video_encoder.h"
+#include "media/base/video_types.h"
+#include "media/base/win/d3d12_mocks.h"
+#include "media/base/win/d3d12_video_mocks.h"
+#include "media/gpu/windows/d3d12_video_encode_av1_delegate.h"
+#include "media/gpu/windows/d3d12_video_encode_h264_delegate.h"
+#include "media/gpu/windows/d3d12_video_helpers.h"
+#include "media/gpu/windows/format_utils.h"
+#include "media/media_buildflags.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/microsoft_dxheaders/src/include/directx/d3dx12_core.h"
+#include "ui/gfx/color_space.h"
+#include "ui/gfx/geometry/size.h"
+
+#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
+#include "media/gpu/windows/d3d12_video_encode_h265_delegate.h"
+#endif  // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
+
+namespace media {
+
+namespace {
+
+using ::testing::_;
+using ::testing::NiceMock;
+using ::testing::Return;
+
+constexpr uint32_t kMinFrameSize = 16;
+constexpr uint32_t kMaxFrameSize = 1920;
+constexpr size_t kMinPayloadSize = 1024;
+constexpr size_t kMaxPayloadSize = 4096;
+
+struct Av1CodecSupportConfig {
+  uint32_t supported_interpolation_filters =
+      1u << D3D12_VIDEO_ENCODER_AV1_INTERPOLATION_FILTERS_EIGHTTAP;
+  D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAGS supported_feature_flags =
+      D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAG_NONE;
+  D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAGS required_feature_flags =
+      D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAG_NONE;
+  std::array<uint32_t, 2> supported_tx_modes = {
+      D3D12_VIDEO_ENCODER_AV1_TX_MODE_FLAG_SELECT,
+      D3D12_VIDEO_ENCODER_AV1_TX_MODE_FLAG_SELECT,
+  };
+  std::array<std::array<D3D12_VIDEO_ENCODER_AV1_RESTORATION_SUPPORT_FLAGS, 3>,
+             3>
+      supported_restoration_params = {};
+  D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES_FLAGS post_encode_flags =
+      D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES_FLAG_NONE;
+};
+
+struct HevcCodecSupportConfig {
+  D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAGS support_flags =
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_NONE;
+  D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE min_luma_cu_size =
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_8x8;
+  D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE max_luma_cu_size =
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_64x64;
+  D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE min_luma_tu_size =
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_4x4;
+  D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE max_luma_tu_size =
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_32x32;
+  uint8_t max_transform_hierarchy_depth_inter = 0;
+  uint8_t max_transform_hierarchy_depth_intra = 0;
+};
+
+struct HevcEncoderSupportConfig {
+  D3D12_VIDEO_ENCODER_SUPPORT_FLAGS support_flags =
+      D3D12_VIDEO_ENCODER_SUPPORT_FLAG_GENERAL_SUPPORT_OK;
+  D3D12_VIDEO_ENCODER_VALIDATION_FLAGS validation_flags =
+      D3D12_VIDEO_ENCODER_VALIDATION_FLAG_NONE;
+  D3D12_VIDEO_ENCODER_PROFILE_HEVC suggested_profile =
+      D3D12_VIDEO_ENCODER_PROFILE_HEVC_MAIN;
+  D3D12_VIDEO_ENCODER_LEVELS_HEVC suggested_level =
+      D3D12_VIDEO_ENCODER_LEVELS_HEVC_31;
+  D3D12_VIDEO_ENCODER_TIER_HEVC suggested_tier =
+      D3D12_VIDEO_ENCODER_TIER_HEVC_MAIN;
+  uint32_t subregion_block_pixels_size = 16;
+};
+
+struct H264PictureControlSupportConfig {
+  uint8_t max_long_term_references = 0;
+  uint8_t max_dpb_capacity = 0;
+};
+
+struct H264CodecConfigurationSupportConfig {
+  D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_H264_FLAGS support_flags =
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_H264_FLAG_NONE;
+  D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_SLICES_DEBLOCKING_MODE_FLAGS
+  deblocking_modes =
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_SLICES_DEBLOCKING_MODE_FLAG_NONE;
+};
+
+Av1CodecSupportConfig ConsumeAv1CodecSupportConfig(
+    FuzzedDataProvider& provider) {
+  Av1CodecSupportConfig config;
+  config.supported_interpolation_filters =
+      provider.ConsumeIntegralInRange<uint32_t>(
+          1u << D3D12_VIDEO_ENCODER_AV1_INTERPOLATION_FILTERS_EIGHTTAP,
+          (1u << D3D12_VIDEO_ENCODER_AV1_INTERPOLATION_FILTERS_SWITCHABLE));
+  for (auto& mode : config.supported_tx_modes) {
+    mode = provider.ConsumeBool()
+               ? D3D12_VIDEO_ENCODER_AV1_TX_MODE_FLAG_SELECT
+               : D3D12_VIDEO_ENCODER_AV1_TX_MODE_FLAG_LARGEST;
+  }
+  const D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAGS base_features =
+      static_cast<D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAGS>(
+          D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAG_CDEF_FILTERING |
+          D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAG_ORDER_HINT_TOOLS |
+          D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAG_LOOP_RESTORATION_FILTER |
+          D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAG_PALETTE_ENCODING |
+          D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAG_INTRA_BLOCK_COPY);
+  if (provider.ConsumeBool()) {
+    config.required_feature_flags =
+        provider.ConsumeBool()
+            ? base_features
+            : static_cast<D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAGS>(base_features &
+                                                                 ~1u);
+  }
+  if (provider.ConsumeBool()) {
+    config.supported_feature_flags = base_features;
+  } else if (config.required_feature_flags !=
+             D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAG_NONE) {
+    config.supported_feature_flags =
+        provider.ConsumeBool() ? config.required_feature_flags
+                               : D3D12_VIDEO_ENCODER_AV1_FEATURE_FLAG_NONE;
+  }
+  for (auto& type_params : config.supported_restoration_params) {
+    for (auto& plane_params : type_params) {
+      uint32_t mask = 0;
+      if (provider.ConsumeBool()) {
+        mask |= D3D12_VIDEO_ENCODER_AV1_RESTORATION_SUPPORT_FLAG_256x256;
+      }
+      if (provider.ConsumeBool()) {
+        mask |= D3D12_VIDEO_ENCODER_AV1_RESTORATION_SUPPORT_FLAG_128x128;
+      }
+      if (provider.ConsumeBool()) {
+        mask |= D3D12_VIDEO_ENCODER_AV1_RESTORATION_SUPPORT_FLAG_64x64;
+      }
+      if (provider.ConsumeBool()) {
+        mask |= D3D12_VIDEO_ENCODER_AV1_RESTORATION_SUPPORT_FLAG_32x32;
+      }
+      plane_params =
+          static_cast<D3D12_VIDEO_ENCODER_AV1_RESTORATION_SUPPORT_FLAGS>(mask);
+    }
+  }
+  static constexpr std::array kPostEncodeFlags = {
+      D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES_FLAG_QUANTIZATION,
+      D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES_FLAG_QUANTIZATION_DELTA,
+      D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES_FLAG_LOOP_FILTER,
+      D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES_FLAG_LOOP_FILTER_DELTA,
+      D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES_FLAG_CDEF_DATA,
+      D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES_FLAG_CONTEXT_UPDATE_TILE_ID,
+      D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES_FLAG_COMPOUND_PREDICTION_MODE,
+      D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES_FLAG_PRIMARY_REF_FRAME,
+      D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES_FLAG_REFERENCE_INDICES,
+  };
+  for (const auto& flag : kPostEncodeFlags) {
+    if (provider.ConsumeBool()) {
+      config.post_encode_flags |= flag;
+    }
+  }
+  return config;
+}
+
+HevcCodecSupportConfig ConsumeHevcCodecSupportConfig(
+    FuzzedDataProvider& provider) {
+  HevcCodecSupportConfig config;
+  static constexpr std::array kCuSizes = {
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_8x8,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_16x16,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_32x32,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_64x64,
+  };
+  static constexpr std::array kTuSizes = {
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_4x4,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_8x8,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_16x16,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_32x32,
+  };
+  auto min_cu = provider.PickValueInArray(kCuSizes);
+  auto max_cu = provider.PickValueInArray(kCuSizes);
+  if (min_cu > max_cu) {
+    std::swap(min_cu, max_cu);
+  }
+  config.min_luma_cu_size = min_cu;
+  config.max_luma_cu_size = max_cu;
+
+  auto min_tu = provider.PickValueInArray(kTuSizes);
+  auto max_tu = provider.PickValueInArray(kTuSizes);
+  if (min_tu > max_tu) {
+    std::swap(min_tu, max_tu);
+  }
+  config.min_luma_tu_size = min_tu;
+  config.max_luma_tu_size = max_tu;
+
+  config.max_transform_hierarchy_depth_inter =
+      provider.ConsumeIntegral<uint8_t>();
+  config.max_transform_hierarchy_depth_intra =
+      provider.ConsumeIntegral<uint8_t>();
+
+  static constexpr std::array kSupportFlags = {
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_BFRAME_LTR_COMBINED_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_INTRA_SLICE_CONSTRAINED_ENCODING_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_CONSTRAINED_INTRAPREDICTION_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_SAO_FILTER_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_ASYMETRIC_MOTION_PARTITION_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_ASYMETRIC_MOTION_PARTITION_REQUIRED,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_TRANSFORM_SKIP_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_DISABLING_LOOP_FILTER_ACROSS_SLICES_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_P_FRAMES_IMPLEMENTED_AS_LOW_DELAY_B_FRAMES,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE_FLAG_SLICE_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_TRANSFORM_SKIP_ROTATION_ENABLED_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_TRANSFORM_SKIP_ROTATION_ENABLED_REQUIRED,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_TRANSFORM_SKIP_CONTEXT_ENABLED_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_TRANSFORM_SKIP_CONTEXT_ENABLED_REQUIRED,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_IMPLICIT_RDPCM_ENABLED_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_IMPLICIT_RDPCM_ENABLED_REQUIRED,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_EXPLICIT_RDPCM_ENABLED_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_EXPLICIT_RDPCM_ENABLED_REQUIRED,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_EXTENDED_PRECISION_PROCESSING_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_EXTENDED_PRECISION_PROCESSING_REQUIRED,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_INTRA_SMOOTHING_DISABLED_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_INTRA_SMOOTHING_DISABLED_REQUIRED,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_HIGH_PRECISION_OFFSETS_ENABLED_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_HIGH_PRECISION_OFFSETS_ENABLED_REQUIRED,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_PERSISTENT_RICE_ADAPTATION_ENABLED_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_PERSISTENT_RICE_ADAPTATION_ENABLED_REQUIRED,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_CABAC_BYPASS_ALIGNMENT_ENABLED_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_CABAC_BYPASS_ALIGNMENT_ENABLED_REQUIRED,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_CROSS_COMPONENT_PREDICTION_ENABLED_FLAG_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_CROSS_COMPONENT_PREDICTION_ENABLED_FLAG_REQUIRED,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_CHROMA_QP_OFFSET_LIST_ENABLED_FLAG_SUPPORT,
+      D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_CHROMA_QP_OFFSET_LIST_ENABLED_FLAG_REQUIRED,
+  };
+  for (auto flag : kSupportFlags) {
+    if (provider.ConsumeBool()) {
+      config.support_flags |= flag;
+    }
+  }
+
+  return config;
+}
+
+HevcEncoderSupportConfig ConsumeHevcEncoderSupportConfig(
+    FuzzedDataProvider& provider) {
+  HevcEncoderSupportConfig config;
+  config.support_flags = D3D12_VIDEO_ENCODER_SUPPORT_FLAG_GENERAL_SUPPORT_OK;
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RATE_CONTROL_RECONFIGURATION_AVAILABLE;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RESOLUTION_RECONFIGURATION_AVAILABLE;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RATE_CONTROL_VBV_SIZE_CONFIG_AVAILABLE;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RATE_CONTROL_FRAME_ANALYSIS_AVAILABLE;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RECONSTRUCTED_FRAMES_REQUIRE_TEXTURE_ARRAYS;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RATE_CONTROL_DELTA_QP_AVAILABLE;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_SUBREGION_LAYOUT_RECONFIGURATION_AVAILABLE;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RATE_CONTROL_ADJUSTABLE_QP_RANGE_AVAILABLE;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RATE_CONTROL_INITIAL_QP_AVAILABLE;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RATE_CONTROL_MAX_FRAME_SIZE_AVAILABLE;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_SEQUENCE_GOP_RECONFIGURATION_AVAILABLE;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_MOTION_ESTIMATION_PRECISION_MODE_LIMIT_AVAILABLE;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RATE_CONTROL_EXTENSION1_SUPPORT;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RATE_CONTROL_QUALITY_VS_SPEED_AVAILABLE;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_SUPPORT_FLAG_READABLE_RECONSTRUCTED_PICTURE_LAYOUT_AVAILABLE;
+  }
+  config.suggested_profile = provider.ConsumeBool()
+                                 ? D3D12_VIDEO_ENCODER_PROFILE_HEVC_MAIN
+                                 : D3D12_VIDEO_ENCODER_PROFILE_HEVC_MAIN10;
+  config.suggested_level = provider.ConsumeBool()
+                               ? D3D12_VIDEO_ENCODER_LEVELS_HEVC_1
+                               : D3D12_VIDEO_ENCODER_LEVELS_HEVC_31;
+  config.subregion_block_pixels_size =
+      provider.ConsumeIntegralInRange<uint32_t>(
+          std::numeric_limits<uint32_t>::min(),
+          std::numeric_limits<uint32_t>::max());
+  return config;
+}
+
+H264PictureControlSupportConfig ConsumeH264PictureControlSupportConfig(
+    FuzzedDataProvider& provider) {
+  H264PictureControlSupportConfig config;
+  config.max_long_term_references = provider.ConsumeIntegral<uint8_t>();
+  config.max_dpb_capacity = provider.ConsumeIntegral<uint8_t>();
+  return config;
+}
+
+H264CodecConfigurationSupportConfig ConsumeH264CodecConfigurationSupportConfig(
+    FuzzedDataProvider& provider) {
+  H264CodecConfigurationSupportConfig config;
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_H264_FLAG_CABAC_ENCODING_SUPPORT;
+  }
+  if (provider.ConsumeBool()) {
+    config.support_flags |=
+        D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_H264_FLAG_INTRA_SLICE_CONSTRAINED_ENCODING_SUPPORT;
+  }
+  if (provider.ConsumeBool()) {
+    config.deblocking_modes |=
+        D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_SLICES_DEBLOCKING_MODE_FLAG_NONE;
+  }
+  if (provider.ConsumeBool()) {
+    config.deblocking_modes |=
+        D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_SLICES_DEBLOCKING_MODE_FLAG_1_DISABLE_ALL_SLICE_BLOCK_EDGES;
+  }
+  if (provider.ConsumeBool()) {
+    config.deblocking_modes |=
+        D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_SLICES_DEBLOCKING_MODE_FLAG_0_ALL_LUMA_CHROMA_SLICE_BLOCK_EDGES_ALWAYS_FILTERED;
+  }
+  if (provider.ConsumeBool()) {
+    config.deblocking_modes |=
+        D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_SLICES_DEBLOCKING_MODE_FLAG_2_DISABLE_SLICE_BOUNDARIES_BLOCKS;
+  }
+  return config;
+}
+
+D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES ConsumeAv1PostEncodeValues(
+    FuzzedDataProvider& provider) {
+  D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES values = {};
+
+  values.CDEF.CdefBits = provider.ConsumeIntegralInRange<uint64_t>(
+      std::numeric_limits<uint64_t>::min(),
+      std::numeric_limits<uint64_t>::max());
+  values.CDEF.CdefDampingMinus3 = provider.ConsumeIntegralInRange<uint64_t>(
+      std::numeric_limits<uint64_t>::min(),
+      std::numeric_limits<uint64_t>::max());
+  for (auto& value : values.CDEF.CdefYPriStrength) {
+    value = provider.ConsumeIntegralInRange<uint64_t>(
+        std::numeric_limits<uint64_t>::min(),
+        std::numeric_limits<uint64_t>::max());
+  }
+  for (auto& value : values.CDEF.CdefYSecStrength) {
+    value = provider.ConsumeIntegralInRange<uint64_t>(
+        std::numeric_limits<uint64_t>::min(),
+        std::numeric_limits<uint64_t>::max());
+  }
+  for (auto& value : values.CDEF.CdefUVPriStrength) {
+    value = provider.ConsumeIntegralInRange<uint64_t>(
+        std::numeric_limits<uint64_t>::min(),
+        std::numeric_limits<uint64_t>::max());
+  }
+  for (auto& value : values.CDEF.CdefUVSecStrength) {
+    value = provider.ConsumeIntegralInRange<uint64_t>(
+        std::numeric_limits<uint64_t>::min(),
+        std::numeric_limits<uint64_t>::max());
+  }
+
+  for (auto& level : values.LoopFilter.LoopFilterLevel) {
+    level = provider.ConsumeIntegralInRange<uint64_t>(
+        std::numeric_limits<uint64_t>::min(),
+        std::numeric_limits<uint64_t>::max());
+  }
+  values.LoopFilter.LoopFilterLevelU =
+      provider.ConsumeIntegralInRange<uint64_t>(
+          std::numeric_limits<uint64_t>::min(),
+          std::numeric_limits<uint64_t>::max());
+  values.LoopFilter.LoopFilterLevelV =
+      provider.ConsumeIntegralInRange<uint64_t>(
+          std::numeric_limits<uint64_t>::min(),
+          std::numeric_limits<uint64_t>::max());
+  values.LoopFilter.LoopFilterSharpnessLevel =
+      provider.ConsumeIntegralInRange<uint64_t>(
+          std::numeric_limits<uint64_t>::min(),
+          std::numeric_limits<uint64_t>::max());
+  values.LoopFilter.LoopFilterDeltaEnabled =
+      provider.ConsumeIntegralInRange<uint64_t>(
+          std::numeric_limits<uint64_t>::min(),
+          std::numeric_limits<uint64_t>::max());
+  values.LoopFilter.UpdateRefDelta = provider.ConsumeIntegralInRange<uint64_t>(
+      std::numeric_limits<uint64_t>::min(),
+      std::numeric_limits<uint64_t>::max());
+  values.LoopFilter.UpdateModeDelta = provider.ConsumeIntegralInRange<uint64_t>(
+      std::numeric_limits<uint64_t>::min(),
+      std::numeric_limits<uint64_t>::max());
+  for (auto& delta : values.LoopFilter.RefDeltas) {
+    delta = provider.ConsumeIntegralInRange<int64_t>(
+        std::numeric_limits<int64_t>::min(),
+        std::numeric_limits<int64_t>::max());
+  }
+  for (auto& delta : values.LoopFilter.ModeDeltas) {
+    delta = provider.ConsumeIntegralInRange<int64_t>(
+        std::numeric_limits<int64_t>::min(),
+        std::numeric_limits<int64_t>::max());
+  }
+
+  values.LoopFilterDelta.DeltaLFPresent =
+      provider.ConsumeIntegralInRange<uint64_t>(
+          std::numeric_limits<uint64_t>::min(),
+          std::numeric_limits<uint64_t>::max());
+  values.LoopFilterDelta.DeltaLFMulti =
+      provider.ConsumeIntegralInRange<uint64_t>(
+          std::numeric_limits<uint64_t>::min(),
+          std::numeric_limits<uint64_t>::max());
+  values.LoopFilterDelta.DeltaLFRes = provider.ConsumeIntegralInRange<uint64_t>(
+      std::numeric_limits<uint64_t>::min(),
+      std::numeric_limits<uint64_t>::max());
+
+  values.Quantization.BaseQIndex = provider.ConsumeIntegralInRange<uint64_t>(
+      std::numeric_limits<uint64_t>::min(),
+      std::numeric_limits<uint64_t>::max());
+  values.Quantization.YDCDeltaQ = provider.ConsumeIntegralInRange<int64_t>(
+      std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max());
+  values.Quantization.UDCDeltaQ = provider.ConsumeIntegralInRange<int64_t>(
+      std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max());
+  values.Quantization.UACDeltaQ = provider.ConsumeIntegralInRange<int64_t>(
+      std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max());
+  values.Quantization.VDCDeltaQ = provider.ConsumeIntegralInRange<int64_t>(
+      std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max());
+  values.Quantization.VACDeltaQ = provider.ConsumeIntegralInRange<int64_t>(
+      std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max());
+  values.Quantization.UsingQMatrix = provider.ConsumeIntegralInRange<uint64_t>(
+      std::numeric_limits<uint64_t>::min(),
+      std::numeric_limits<uint64_t>::max());
+  values.Quantization.QMY = provider.ConsumeIntegralInRange<uint64_t>(
+      std::numeric_limits<uint64_t>::min(),
+      std::numeric_limits<uint64_t>::max());
+  values.Quantization.QMU = provider.ConsumeIntegralInRange<uint64_t>(
+      std::numeric_limits<uint64_t>::min(),
+      std::numeric_limits<uint64_t>::max());
+  values.Quantization.QMV = provider.ConsumeIntegralInRange<uint64_t>(
+      std::numeric_limits<uint64_t>::min(),
+      std::numeric_limits<uint64_t>::max());
+
+  values.QuantizationDelta.DeltaQPresent =
+      provider.ConsumeIntegralInRange<uint64_t>(
+          std::numeric_limits<uint64_t>::min(),
+          std::numeric_limits<uint64_t>::max());
+  values.QuantizationDelta.DeltaQRes =
+      provider.ConsumeIntegralInRange<uint64_t>(
+          std::numeric_limits<uint64_t>::min(),
+          std::numeric_limits<uint64_t>::max());
+
+  if (provider.ConsumeBool()) {
+    values.SegmentationConfig.NumSegments =
+        provider.ConsumeIntegralInRange<uint64_t>(
+            std::numeric_limits<uint64_t>::min(),
+            std::numeric_limits<uint64_t>::max());
+  } else {
+    values.SegmentationConfig.NumSegments = 0;
+  }
+  values.SegmentationConfig.UpdateMap =
+      provider.ConsumeIntegralInRange<uint64_t>(
+          std::numeric_limits<uint64_t>::min(),
+          std::numeric_limits<uint64_t>::max());
+  values.SegmentationConfig.TemporalUpdate =
+      provider.ConsumeIntegralInRange<uint64_t>(
+          std::numeric_limits<uint64_t>::min(),
+          std::numeric_limits<uint64_t>::max());
+  values.SegmentationConfig.UpdateData =
+      provider.ConsumeIntegralInRange<uint64_t>(
+          std::numeric_limits<uint64_t>::min(),
+          std::numeric_limits<uint64_t>::max());
+  for (auto& segment : values.SegmentationConfig.SegmentsData) {
+    segment.EnabledFeatures = provider.ConsumeIntegralInRange<uint64_t>(
+        std::numeric_limits<uint64_t>::min(),
+        std::numeric_limits<uint64_t>::max());
+    for (auto& value : segment.FeatureValue) {
+      value = provider.ConsumeIntegralInRange<int64_t>(
+          std::numeric_limits<int64_t>::min(),
+          std::numeric_limits<int64_t>::max());
+    }
+  }
+
+  values.PrimaryRefFrame = provider.ConsumeIntegralInRange<uint64_t>(
+      std::numeric_limits<uint64_t>::min(),
+      std::numeric_limits<uint64_t>::max());
+  for (auto& index : values.ReferenceIndices) {
+    index = provider.ConsumeIntegralInRange<uint64_t>(
+        std::numeric_limits<uint64_t>::min(),
+        std::numeric_limits<uint64_t>::max());
+  }
+  values.CompoundPredictionType = provider.ConsumeIntegralInRange<uint64_t>(
+      std::numeric_limits<uint64_t>::min(),
+      std::numeric_limits<uint64_t>::max());
+
+  return values;
+}
+
+class FuzzerVideoEncoderWrapper : public D3D12VideoEncoderWrapper {
+ public:
+  FuzzerVideoEncoderWrapper(
+      size_t payload_size,
+      D3D12_VIDEO_ENCODER_CODEC codec,
+      std::optional<D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES>
+          av1_post_encode_values)
+      : D3D12VideoEncoderWrapper(nullptr, nullptr),
+        payload_size_(payload_size),
+        metadata_resource_(MakeComPtr<NiceMock<D3D12ResourceMock>>()) {
+    size_t metadata_size = sizeof(D3D12_VIDEO_ENCODER_OUTPUT_METADATA);
+    if (codec == D3D12_VIDEO_ENCODER_CODEC_AV1) {
+      metadata_size +=
+          sizeof(D3D12_VIDEO_ENCODER_FRAME_SUBREGION_METADATA) +
+          sizeof(
+              D3D12_VIDEO_ENCODER_AV1_PICTURE_CONTROL_SUBREGIONS_LAYOUT_DATA_TILES) +
+          sizeof(D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES);
+    }
+    metadata_bytes_.resize(metadata_size);
+    D3D12_VIDEO_ENCODER_OUTPUT_METADATA metadata = {};
+    metadata.EncodedBitstreamWrittenBytesCount = payload_size_;
+    metadata.WrittenSubregionsCount = 1;
+    base::span(metadata_bytes_)
+        .first(sizeof(metadata))
+        .copy_from(base::byte_span_from_ref(metadata));
+
+    if (codec == D3D12_VIDEO_ENCODER_CODEC_AV1) {
+      size_t offset = sizeof(D3D12_VIDEO_ENCODER_OUTPUT_METADATA);
+      D3D12_VIDEO_ENCODER_FRAME_SUBREGION_METADATA subregion = {};
+      subregion.bSize = static_cast<UINT64>(payload_size_);
+      base::span(metadata_bytes_)
+          .subspan(offset, sizeof(subregion))
+          .copy_from(base::byte_span_from_ref(subregion));
+      offset += sizeof(D3D12_VIDEO_ENCODER_FRAME_SUBREGION_METADATA);
+
+      D3D12_VIDEO_ENCODER_AV1_PICTURE_CONTROL_SUBREGIONS_LAYOUT_DATA_TILES
+      tiles = {};
+      base::span(metadata_bytes_)
+          .subspan(offset, sizeof(tiles))
+          .copy_from(base::byte_span_from_ref(tiles));
+      offset += sizeof(
+          D3D12_VIDEO_ENCODER_AV1_PICTURE_CONTROL_SUBREGIONS_LAYOUT_DATA_TILES);
+
+      D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES post_encode_values = {};
+      if (av1_post_encode_values.has_value()) {
+        post_encode_values = av1_post_encode_values.value();
+      }
+      base::span(metadata_bytes_)
+          .subspan(offset, sizeof(post_encode_values))
+          .copy_from(base::byte_span_from_ref(post_encode_values));
+    }
+
+    D3D12_RESOURCE_DESC metadata_desc = CD3DX12_RESOURCE_DESC::Buffer(
+        static_cast<UINT64>(metadata_bytes_.size()));
+    ON_CALL(*metadata_resource_.Get(), GetDesc)
+        .WillByDefault(Return(metadata_desc));
+    ON_CALL(*metadata_resource_.Get(), Map)
+        .WillByDefault([this](UINT, const D3D12_RANGE*, void** data) {
+          *data = metadata_bytes_.data();
+          return S_OK;
+        });
+    ON_CALL(*metadata_resource_.Get(), Unmap)
+        .WillByDefault([](UINT, const D3D12_RANGE*) {});
+  }
+
+  bool Initialize(uint32_t) override { return true; }
+  bool Wait(D3D12FenceAndValue) override { return true; }
+  EncoderStatus Encode(
+      const D3D12_VIDEO_ENCODER_ENCODEFRAME_INPUT_ARGUMENTS&,
+      const D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE&) override {
+    return EncoderStatus::Codes::kOk;
+  }
+
+  EncoderStatus::Or<ScopedD3D12ResourceMap> GetEncoderOutputMetadata()
+      const override {
+    ScopedD3D12ResourceMap map;
+    if (!map.Map(metadata_resource_.Get(), 0, nullptr)) {
+      return EncoderStatus::Codes::kEncoderInitializationError;
+    }
+    return map;
+  }
+
+  EncoderStatus ReadbackBitstream(base::span<uint8_t> data) const override {
+    std::fill(data.begin(), data.end(), 0);
+    return EncoderStatus::Codes::kOk;
+  }
+
+ private:
+  size_t payload_size_ = 0;
+  mutable std::vector<uint8_t> metadata_bytes_;
+  Microsoft::WRL::ComPtr<D3D12ResourceMock> metadata_resource_;
+};
+
+class FuzzerVideoProcessorWrapper : public D3D12VideoProcessorWrapper {
+ public:
+  explicit FuzzerVideoProcessorWrapper(
+      Microsoft::WRL::ComPtr<ID3D12VideoDevice> device)
+      : D3D12VideoProcessorWrapper(std::move(device)),
+        fence_(MakeComPtr<NiceMock<D3D12FenceMock>>()) {}
+
+  bool Init() override { return true; }
+  bool Wait(D3D12FenceAndValue) override { return true; }
+
+  D3D12FenceAndValue ProcessFrames(ID3D12Resource*,
+                                   UINT,
+                                   const gfx::ColorSpace&,
+                                   const gfx::Rect&,
+                                   ID3D12Resource*,
+                                   UINT,
+                                   const gfx::ColorSpace&,
+                                   const gfx::Rect&) override {
+    return {fence_, 1};
+  }
+
+ private:
+  Microsoft::WRL::ComPtr<ID3D12Fence> fence_;
+};
+
+std::unique_ptr<D3D12VideoEncoderWrapper> CreateFuzzerVideoEncoderWrapper(
+    size_t payload_size,
+    ID3D12VideoDevice*,
+    D3D12_VIDEO_ENCODER_CODEC codec,
+    const D3D12_VIDEO_ENCODER_PROFILE_DESC&,
+    const D3D12_VIDEO_ENCODER_LEVEL_SETTING&,
+    DXGI_FORMAT,
+    const D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION&,
+    const D3D12_VIDEO_ENCODER_PICTURE_RESOLUTION_DESC&) {
+  return std::make_unique<FuzzerVideoEncoderWrapper>(payload_size, codec,
+                                                     std::nullopt);
+}
+
+std::unique_ptr<D3D12VideoEncoderWrapper> CreateFuzzerVideoEncoderWrapperAV1(
+    size_t payload_size,
+    D3D12_VIDEO_ENCODER_AV1_POST_ENCODE_VALUES post_encode_values,
+    ID3D12VideoDevice*,
+    D3D12_VIDEO_ENCODER_CODEC codec,
+    const D3D12_VIDEO_ENCODER_PROFILE_DESC&,
+    const D3D12_VIDEO_ENCODER_LEVEL_SETTING&,
+    DXGI_FORMAT,
+    const D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION&,
+    const D3D12_VIDEO_ENCODER_PICTURE_RESOLUTION_DESC&) {
+  return std::make_unique<FuzzerVideoEncoderWrapper>(payload_size, codec,
+                                                     post_encode_values);
+}
+
+std::unique_ptr<D3D12VideoProcessorWrapper> CreateFuzzerVideoProcessorWrapper(
+    Microsoft::WRL::ComPtr<ID3D12VideoDevice>&& video_device) {
+  return std::make_unique<FuzzerVideoProcessorWrapper>(std::move(video_device));
+}
+
+gfx::Size ConsumeFrameSize(FuzzedDataProvider& provider) {
+  uint32_t width =
+      provider.ConsumeIntegralInRange<uint32_t>(kMinFrameSize, kMaxFrameSize);
+  uint32_t height =
+      provider.ConsumeIntegralInRange<uint32_t>(kMinFrameSize, kMaxFrameSize);
+  // 4:2:0 subsampling requires frame size to be even.
+  width &= ~1u;
+  height &= ~1u;
+  width = std::max(width, kMinFrameSize);
+  height = std::max(height, kMinFrameSize);
+  return gfx::Size(width, height);
+}
+
+Microsoft::WRL::ComPtr<D3D12ResourceMock> CreateInputResource(
+    const gfx::Size& size,
+    DXGI_FORMAT format) {
+  auto resource = MakeComPtr<NiceMock<D3D12ResourceMock>>();
+  D3D12_RESOURCE_DESC desc =
+      CD3DX12_RESOURCE_DESC::Tex2D(format, size.width(), size.height(), 1, 1);
+  ON_CALL(*resource.Get(), GetDesc).WillByDefault(Return(desc));
+  return resource;
+}
+
+void ConfigureDeviceCommon(
+    Microsoft::WRL::ComPtr<D3D12DeviceMock> device,
+    Microsoft::WRL::ComPtr<D3D12VideoDevice3Mock> video_device) {
+  ON_CALL(*video_device.Get(), QueryInterface(IID_ID3D12Device, _))
+      .WillByDefault(SetComPointeeAndReturnOk<1>(device.Get()));
+  ON_CALL(*video_device.Get(), QueryInterface(IID_ID3D12VideoDevice1, _))
+      .WillByDefault(SetComPointeeAndReturnOk<1>(video_device.Get()));
+
+  ON_CALL(*device.Get(), CreateCommittedResource)
+      .WillByDefault([](const D3D12_HEAP_PROPERTIES*, D3D12_HEAP_FLAGS,
+                        const D3D12_RESOURCE_DESC* desc, D3D12_RESOURCE_STATES,
+                        const D3D12_CLEAR_VALUE*, REFIID, void** ppv) {
+        auto resource = MakeComPtr<NiceMock<D3D12ResourceMock>>();
+        D3D12_RESOURCE_DESC copy = *desc;
+        ON_CALL(*resource.Get(), GetDesc).WillByDefault(Return(copy));
+        resource->AddRef();
+        *ppv = resource.Get();
+        return S_OK;
+      });
+}
+
+void ConfigureVideoDeviceForH264(
+    D3D12VideoDevice3Mock* video_device,
+    const H264PictureControlSupportConfig& picture_support_config,
+    const H264CodecConfigurationSupportConfig& codec_support_config) {
+  ON_CALL(*video_device, CheckFeatureSupport)
+      .WillByDefault([picture_support_config, codec_support_config](
+                         D3D12_FEATURE_VIDEO feature, void* data, UINT) {
+        switch (feature) {
+          case D3D12_FEATURE_VIDEO_ENCODER_CODEC: {
+            auto* codec =
+                static_cast<D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC*>(data);
+            codec->IsSupported = codec->Codec == D3D12_VIDEO_ENCODER_CODEC_H264;
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_PROFILE_LEVEL: {
+            auto* profile_level =
+                static_cast<D3D12_FEATURE_DATA_VIDEO_ENCODER_PROFILE_LEVEL*>(
+                    data);
+            profile_level->IsSupported = true;
+            if (profile_level->MinSupportedLevel.pH264LevelSetting) {
+              *profile_level->MinSupportedLevel.pH264LevelSetting =
+                  D3D12_VIDEO_ENCODER_LEVELS_H264_1;
+            }
+            if (profile_level->MaxSupportedLevel.pH264LevelSetting) {
+              *profile_level->MaxSupportedLevel.pH264LevelSetting =
+                  D3D12_VIDEO_ENCODER_LEVELS_H264_31;
+            }
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_INPUT_FORMAT: {
+            auto* input_format =
+                static_cast<D3D12_FEATURE_DATA_VIDEO_ENCODER_INPUT_FORMAT*>(
+                    data);
+            input_format->IsSupported = true;
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT: {
+            auto* picture_control = static_cast<
+                D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT*>(
+                data);
+            picture_control->IsSupported = true;
+            if (picture_control->PictureSupport.pH264Support) {
+              picture_control->PictureSupport.pH264Support
+                  ->MaxLongTermReferences =
+                  picture_support_config.max_long_term_references;
+              picture_control->PictureSupport.pH264Support->MaxDPBCapacity =
+                  picture_support_config.max_dpb_capacity;
+            }
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT: {
+            auto* config = static_cast<
+                D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT*>(
+                data);
+            config->IsSupported = true;
+            if (config->CodecSupportLimits.pH264Support) {
+              config->CodecSupportLimits.pH264Support->SupportFlags =
+                  codec_support_config.support_flags;
+              config->CodecSupportLimits.pH264Support
+                  ->DisableDeblockingFilterSupportedModes =
+                  codec_support_config.deblocking_modes;
+            }
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_SUPPORT: {
+            auto* support =
+                static_cast<D3D12_FEATURE_DATA_VIDEO_ENCODER_SUPPORT*>(data);
+            support->SupportFlags =
+                D3D12_VIDEO_ENCODER_SUPPORT_FLAG_GENERAL_SUPPORT_OK;
+            support->ValidationFlags = D3D12_VIDEO_ENCODER_VALIDATION_FLAG_NONE;
+            if (support->SuggestedProfile.pH264Profile) {
+              *support->SuggestedProfile.pH264Profile =
+                  D3D12_VIDEO_ENCODER_PROFILE_H264_MAIN;
+            }
+            if (support->SuggestedLevel.pH264LevelSetting) {
+              *support->SuggestedLevel.pH264LevelSetting =
+                  D3D12_VIDEO_ENCODER_LEVELS_H264_31;
+            }
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE: {
+            auto* rate_control = static_cast<
+                D3D12_FEATURE_DATA_VIDEO_ENCODER_RATE_CONTROL_MODE*>(data);
+            rate_control->IsSupported = true;
+            return S_OK;
+          }
+          default:
+            return E_INVALIDARG;
+        }
+      });
+}
+
+void ConfigureVideoDeviceForAV1(D3D12VideoDevice3Mock* video_device,
+                                const Av1CodecSupportConfig& support_config) {
+  ON_CALL(*video_device, CheckFeatureSupport)
+      .WillByDefault([support_config](D3D12_FEATURE_VIDEO feature, void* data,
+                                      UINT) {
+        switch (feature) {
+          case D3D12_FEATURE_VIDEO_ENCODER_CODEC: {
+            auto* codec =
+                static_cast<D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC*>(data);
+            codec->IsSupported = codec->Codec == D3D12_VIDEO_ENCODER_CODEC_AV1;
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_PROFILE_LEVEL: {
+            auto* profile_level =
+                static_cast<D3D12_FEATURE_DATA_VIDEO_ENCODER_PROFILE_LEVEL*>(
+                    data);
+            profile_level->IsSupported = true;
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_INPUT_FORMAT: {
+            auto* input_format =
+                static_cast<D3D12_FEATURE_DATA_VIDEO_ENCODER_INPUT_FORMAT*>(
+                    data);
+            input_format->IsSupported = true;
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT: {
+            auto* config_support = static_cast<
+                D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT*>(
+                data);
+            config_support->IsSupported = true;
+            if (config_support->CodecSupportLimits.pAV1Support) {
+              auto* av1 = config_support->CodecSupportLimits.pAV1Support;
+              av1->SupportedInterpolationFilters = static_cast<
+                  D3D12_VIDEO_ENCODER_AV1_INTERPOLATION_FILTERS_FLAGS>(
+                  support_config.supported_interpolation_filters);
+              av1->SupportedFeatureFlags =
+                  support_config.supported_feature_flags;
+              av1->RequiredFeatureFlags = support_config.required_feature_flags;
+              av1->PostEncodeValuesFlags = support_config.post_encode_flags;
+              const size_t tx_mode_count =
+                  std::min(std::size(av1->SupportedTxModes),
+                           support_config.supported_tx_modes.size());
+              auto src_tx_modes = base::span(support_config.supported_tx_modes);
+              auto src_tx_mode_it = src_tx_modes.begin();
+              size_t tx_index = 0;
+              for (auto& mode : av1->SupportedTxModes) {
+                if (tx_index < tx_mode_count) {
+                  mode = static_cast<D3D12_VIDEO_ENCODER_AV1_TX_MODE_FLAGS>(
+                      *src_tx_mode_it);
+                  ++src_tx_mode_it;
+                } else {
+                  mode = D3D12_VIDEO_ENCODER_AV1_TX_MODE_FLAG_SELECT;
+                }
+                ++tx_index;
+              }
+              auto src_restoration =
+                  base::span(support_config.supported_restoration_params);
+              auto src_restoration_it = src_restoration.begin();
+              for (auto& type_params : av1->SupportedRestorationParams) {
+                auto src_plane_it = src_restoration_it->begin();
+                for (auto& plane_params : type_params) {
+                  plane_params = *src_plane_it;
+                  ++src_plane_it;
+                }
+                ++src_restoration_it;
+              }
+            }
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_SUPPORT1: {
+            auto* support =
+                static_cast<D3D12_FEATURE_DATA_VIDEO_ENCODER_SUPPORT1*>(data);
+            support->SupportFlags =
+                D3D12_VIDEO_ENCODER_SUPPORT_FLAG_GENERAL_SUPPORT_OK;
+            support->ValidationFlags = D3D12_VIDEO_ENCODER_VALIDATION_FLAG_NONE;
+            if (support->SuggestedProfile.pAV1Profile) {
+              *support->SuggestedProfile.pAV1Profile =
+                  D3D12_VIDEO_ENCODER_AV1_PROFILE_MAIN;
+            }
+            if (support->SuggestedLevel.pAV1LevelSetting) {
+              support->SuggestedLevel.pAV1LevelSetting->Level =
+                  D3D12_VIDEO_ENCODER_AV1_LEVELS_3_1;
+              support->SuggestedLevel.pAV1LevelSetting->Tier =
+                  D3D12_VIDEO_ENCODER_AV1_TIER_MAIN;
+            }
+            if (support->pResolutionDependentSupport) {
+              support->pResolutionDependentSupport[0].SubregionBlockPixelsSize =
+                  16;
+            }
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE: {
+            auto* rate_control = static_cast<
+                D3D12_FEATURE_DATA_VIDEO_ENCODER_RATE_CONTROL_MODE*>(data);
+            rate_control->IsSupported = true;
+            return S_OK;
+          }
+          default:
+            return E_INVALIDARG;
+        }
+      });
+}
+
+#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
+void ConfigureVideoDeviceForH265(
+    D3D12VideoDevice3Mock* video_device,
+    const HevcCodecSupportConfig& support_config,
+    const HevcEncoderSupportConfig& encoder_config) {
+  ON_CALL(*video_device, CheckFeatureSupport)
+      .WillByDefault([support_config, encoder_config](
+                         D3D12_FEATURE_VIDEO feature, void* data, UINT) {
+        switch (feature) {
+          case D3D12_FEATURE_VIDEO_ENCODER_CODEC: {
+            auto* codec =
+                static_cast<D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC*>(data);
+            codec->IsSupported = codec->Codec == D3D12_VIDEO_ENCODER_CODEC_HEVC;
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_PROFILE_LEVEL: {
+            auto* profile_level =
+                static_cast<D3D12_FEATURE_DATA_VIDEO_ENCODER_PROFILE_LEVEL*>(
+                    data);
+            profile_level->IsSupported = true;
+            if (profile_level->MinSupportedLevel.pHEVCLevelSetting) {
+              *profile_level->MinSupportedLevel.pHEVCLevelSetting = {
+                  D3D12_VIDEO_ENCODER_LEVELS_HEVC_1,
+                  D3D12_VIDEO_ENCODER_TIER_HEVC_MAIN};
+            }
+            if (profile_level->MaxSupportedLevel.pHEVCLevelSetting) {
+              *profile_level->MaxSupportedLevel.pHEVCLevelSetting = {
+                  D3D12_VIDEO_ENCODER_LEVELS_HEVC_31,
+                  D3D12_VIDEO_ENCODER_TIER_HEVC_MAIN};
+            }
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_INPUT_FORMAT: {
+            auto* input_format =
+                static_cast<D3D12_FEATURE_DATA_VIDEO_ENCODER_INPUT_FORMAT*>(
+                    data);
+            input_format->IsSupported = true;
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT: {
+            auto* picture_control = static_cast<
+                D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT*>(
+                data);
+            picture_control->IsSupported = true;
+            if (picture_control->PictureSupport.pHEVCSupport) {
+              picture_control->PictureSupport.pHEVCSupport
+                  ->MaxLongTermReferences = 1;
+              picture_control->PictureSupport.pHEVCSupport->MaxDPBCapacity = 16;
+            }
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT: {
+            auto* config = static_cast<
+                D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT*>(
+                data);
+            config->IsSupported = true;
+            if (config->CodecSupportLimits.pHEVCSupport) {
+              *config->CodecSupportLimits.pHEVCSupport = {
+                  .SupportFlags = support_config.support_flags,
+                  .MinLumaCodingUnitSize = support_config.min_luma_cu_size,
+                  .MaxLumaCodingUnitSize = support_config.max_luma_cu_size,
+                  .MinLumaTransformUnitSize = support_config.min_luma_tu_size,
+                  .MaxLumaTransformUnitSize = support_config.max_luma_tu_size,
+                  .max_transform_hierarchy_depth_inter =
+                      support_config.max_transform_hierarchy_depth_inter,
+                  .max_transform_hierarchy_depth_intra =
+                      support_config.max_transform_hierarchy_depth_intra,
+              };
+            }
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_SUPPORT: {
+            auto* support =
+                static_cast<D3D12_FEATURE_DATA_VIDEO_ENCODER_SUPPORT*>(data);
+            support->SupportFlags = encoder_config.support_flags;
+            support->ValidationFlags = encoder_config.validation_flags;
+            if (support->SuggestedProfile.pHEVCProfile) {
+              *support->SuggestedProfile.pHEVCProfile =
+                  encoder_config.suggested_profile;
+            }
+            if (support->SuggestedLevel.pHEVCLevelSetting) {
+              *support->SuggestedLevel.pHEVCLevelSetting = {
+                  encoder_config.suggested_level,
+                  encoder_config.suggested_tier};
+            }
+            if (support->pResolutionDependentSupport) {
+              support->pResolutionDependentSupport[0].SubregionBlockPixelsSize =
+                  encoder_config.subregion_block_pixels_size;
+            }
+            return S_OK;
+          }
+          case D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE: {
+            auto* rate_control = static_cast<
+                D3D12_FEATURE_DATA_VIDEO_ENCODER_RATE_CONTROL_MODE*>(data);
+            rate_control->IsSupported = true;
+            return S_OK;
+          }
+          default:
+            return E_INVALIDARG;
+        }
+      });
+}
+#endif  // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
+
+VideoEncodeAccelerator::Config BuildConfig(VideoCodecProfile profile,
+                                           VideoPixelFormat format,
+                                           const gfx::Size& size,
+                                           uint32_t bitrate,
+                                           uint32_t framerate,
+                                           bool is_screen) {
+  VideoEncodeAccelerator::Config config;
+  config.input_format = format;
+  config.input_visible_size = size;
+  config.output_profile = profile;
+  config.bitrate = Bitrate::ConstantBitrate(bitrate);
+  config.framerate = framerate;
+  config.storage_type = VideoEncodeAccelerator::Config::StorageType::kShmem;
+  config.content_type =
+      is_screen ? VideoEncodeAccelerator::Config::ContentType::kDisplay
+                : VideoEncodeAccelerator::Config::ContentType::kCamera;
+  config.manual_reference_buffer_control = is_screen;
+  config.gop_length = framerate * 2;
+  return config;
+}
+
+}  // namespace
+
+int RunD3D12VideoEncodeDelegateFuzzer(FuzzedDataProvider& provider) {
+  auto device = MakeComPtr<NiceMock<D3D12DeviceMock>>();
+  auto video_device = MakeComPtr<NiceMock<D3D12VideoDevice3Mock>>();
+  ConfigureDeviceCommon(device, video_device);
+
+  size_t payload_size =
+      provider.ConsumeIntegralInRange<size_t>(kMinPayloadSize, kMaxPayloadSize);
+  auto processor_factory =
+      base::BindRepeating(&CreateFuzzerVideoProcessorWrapper);
+
+  gfx::Size frame_size = ConsumeFrameSize(provider);
+  uint32_t bitrate = provider.ConsumeIntegralInRange<uint32_t>(10000, 2000000);
+  uint32_t framerate = provider.ConsumeIntegralInRange<uint32_t>(1, 60);
+  bool is_screen = provider.ConsumeBool();
+
+  enum class CodecChoice { kH264, kH265, kAV1 };
+  CodecChoice codec =
+      provider.ConsumeBool() ? CodecChoice::kH264 : CodecChoice::kAV1;
+#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
+  if (provider.ConsumeBool()) {
+    codec = CodecChoice::kH265;
+  }
+#endif  // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
+
+  std::unique_ptr<D3D12VideoEncodeDelegate> delegate;
+  VideoEncodeAccelerator::Config config;
+  VideoPixelFormat input_format = PIXEL_FORMAT_NV12;
+
+  switch (codec) {
+    case CodecChoice::kH264: {
+      auto h264_picture_support_config =
+          ConsumeH264PictureControlSupportConfig(provider);
+      auto h264_codec_support_config =
+          ConsumeH264CodecConfigurationSupportConfig(provider);
+      ConfigureVideoDeviceForH264(video_device.Get(),
+                                  h264_picture_support_config,
+                                  h264_codec_support_config);
+      bool use_high10 = provider.ConsumeBool();
+      VideoCodecProfile profile =
+          use_high10 ? H264PROFILE_HIGH10PROFILE : H264PROFILE_MAIN;
+      input_format = use_high10 ? PIXEL_FORMAT_P010LE : PIXEL_FORMAT_NV12;
+      config = BuildConfig(profile, input_format, frame_size, bitrate,
+                           framerate, is_screen);
+      delegate = std::make_unique<D3D12VideoEncodeH264Delegate>(
+          video_device, gpu::GpuDriverBugWorkarounds{});
+      auto encoder_factory =
+          base::BindRepeating(&CreateFuzzerVideoEncoderWrapper, payload_size);
+      delegate->SetFactoriesForTesting(encoder_factory, processor_factory);
+      break;
+    }
+#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
+    case CodecChoice::kH265: {
+      auto hevc_support_config = ConsumeHevcCodecSupportConfig(provider);
+      auto hevc_encoder_support_config =
+          ConsumeHevcEncoderSupportConfig(provider);
+      ConfigureVideoDeviceForH265(video_device.Get(), hevc_support_config,
+                                  hevc_encoder_support_config);
+      bool use_main10 = provider.ConsumeBool();
+      VideoCodecProfile profile =
+          use_main10 ? HEVCPROFILE_MAIN10 : HEVCPROFILE_MAIN;
+      input_format = use_main10 ? PIXEL_FORMAT_P010LE : PIXEL_FORMAT_NV12;
+      config = BuildConfig(profile, input_format, frame_size, bitrate,
+                           framerate, is_screen);
+      delegate = std::make_unique<D3D12VideoEncodeH265Delegate>(
+          video_device, gpu::GpuDriverBugWorkarounds{});
+      auto encoder_factory =
+          base::BindRepeating(&CreateFuzzerVideoEncoderWrapper, payload_size);
+      delegate->SetFactoriesForTesting(encoder_factory, processor_factory);
+      break;
+    }
+#endif  // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
+    case CodecChoice::kAV1: {
+      auto av1_support_config = ConsumeAv1CodecSupportConfig(provider);
+      ConfigureVideoDeviceForAV1(video_device.Get(), av1_support_config);
+      input_format = PIXEL_FORMAT_NV12;
+      config = BuildConfig(AV1PROFILE_PROFILE_MAIN, input_format, frame_size,
+                           bitrate, framerate, is_screen);
+      delegate = std::make_unique<D3D12VideoEncodeAV1Delegate>(
+          video_device, gpu::GpuDriverBugWorkarounds{});
+      auto post_encode_values = ConsumeAv1PostEncodeValues(provider);
+      auto encoder_factory =
+          base::BindRepeating(&CreateFuzzerVideoEncoderWrapperAV1, payload_size,
+                              post_encode_values);
+      delegate->SetFactoriesForTesting(encoder_factory, processor_factory);
+      break;
+    }
+  }
+
+  if (!delegate->Initialize(config).is_ok()) {
+    return 0;
+  }
+
+  DXGI_FORMAT dxgi_format = VideoPixelFormatToDxgiFormat(input_format);
+  auto input_resource = CreateInputResource(frame_size, dxgi_format);
+  D3D12PictureBuffer picture_buffer(input_resource, 0, {});
+
+  auto shared_memory = base::UnsafeSharedMemoryRegion::Create(payload_size);
+  BitstreamBuffer bitstream_buffer(provider.ConsumeIntegral<int32_t>(),
+                                   shared_memory.Duplicate(), payload_size);
+
+  VideoEncoder::EncodeOptions options(provider.ConsumeBool());
+  if (provider.ConsumeBool()) {
+    options.quantizer = provider.ConsumeIntegralInRange<int>(1, 51);
+  }
+  if (provider.ConsumeBool()) {
+    size_t max_refs = delegate->GetMaxNumOfManualRefBuffers();
+    if (max_refs > 0) {
+      size_t ref_count = provider.ConsumeIntegralInRange<size_t>(0, max_refs);
+      for (size_t i = 0; i < ref_count; ++i) {
+        options.reference_buffers.push_back(
+            static_cast<uint8_t>(provider.ConsumeIntegralInRange<int>(
+                0, static_cast<int>(max_refs - 1))));
+      }
+      if (provider.ConsumeBool()) {
+        options.update_buffer = static_cast<uint8_t>(
+            provider.ConsumeIntegralInRange<int>(0, max_refs - 1));
+      }
+    }
+  }
+
+  gfx::ColorSpace color_space = gfx::ColorSpace::CreateREC709();
+  delegate->Encode(picture_buffer, color_space, bitstream_buffer, options);
+
+  return 0;
+}
+
+}  // namespace media
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  FuzzedDataProvider provider(data, size);
+  return media::RunD3D12VideoEncodeDelegateFuzzer(provider);
+}
diff --git a/media/gpu/windows/d3d12_video_encode_h265_delegate.cc b/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
index 3419679b..3592d00 100644
--- a/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
@@ -402,18 +402,18 @@
   // Rate control.
   int qp = -1;
   if (software_rate_controller_) {
-    software_rate_controller_
-        ->temporal_layers(metadata_.svc_generic->temporal_idx)
+    size_t temporal_idx =
+        metadata_.svc_generic ? metadata_.svc_generic->temporal_idx : 0;
+    software_rate_controller_->temporal_layers(temporal_idx)
         .ShrinkHRDBuffer(rate_controller_timestamp_);
     if (is_keyframe) {
       software_rate_controller_->EstimateIntraFrameQP(
           rate_controller_timestamp_);
     } else {
       software_rate_controller_->EstimateInterFrameQP(
-          metadata_.svc_generic->temporal_idx, rate_controller_timestamp_);
+          temporal_idx, rate_controller_timestamp_);
     }
-    qp = software_rate_controller_
-             ->temporal_layers(metadata_.svc_generic->temporal_idx)
+    qp = software_rate_controller_->temporal_layers(temporal_idx)
              .curr_frame_qp();
   } else if (options.quantizer.has_value()) {
     qp = options.quantizer.value();
@@ -645,6 +645,14 @@
   if (!status.is_ok()) {
     return status;
   }
+  if (!std::has_single_bit(
+          resolution_support_limits_.SubregionBlockPixelsSize)) {
+    return {
+        EncoderStatus::Codes::kEncoderUnsupportedConfig,
+        base::StringPrintf(
+            "D3D12VideoEncoder reported invalid SubregionBlockPixelsSize %u",
+            resolution_support_limits_.SubregionBlockPixelsSize)};
+  }
   encoder_support_flags_ = support.SupportFlags;
 
   h265_level_ = suggested_level;
diff --git a/mojo/public/rust/bindings/message.rs b/mojo/public/rust/bindings/message.rs
index aaa3e90..85c4b83 100644
--- a/mojo/public/rust/bindings/message.rs
+++ b/mojo/public/rust/bindings/message.rs
@@ -40,7 +40,7 @@
     pub fn from_raw(msg: &RawMojoMessage) -> ParsingResult<Self> {
         // FOR_RELEASE: Make sure any MojoErrors are handled gracefully.
         let (raw_bytes, handles) = msg.read_data().unwrap();
-        let (remaining_bytes, header) = MessageHeader::deserialize(&raw_bytes)?;
+        let (remaining_bytes, header) = MessageHeader::deserialize(raw_bytes)?;
         // FOR_RELEASE: Hopefully once we make our MojomMessage type better we
         // can avoid calling to_vec here.
         let payload = remaining_bytes.to_vec();
diff --git a/mojo/public/rust/bindings/test/tests.rs b/mojo/public/rust/bindings/test/tests.rs
index 6196401dd..113c4dca 100644
--- a/mojo/public/rust/bindings/test/tests.rs
+++ b/mojo/public/rust/bindings/test/tests.rs
@@ -390,7 +390,7 @@
     let _receiver = pending_receiver.bind_with_options(
         SaturatingMathService {},
         None,
-        Some(Box::new(move || (quit_loop)())),
+        Some(Box::new(quit_loop)),
     );
     drop(pending_remote);
 
@@ -402,7 +402,7 @@
 
     // Test Receiver disconnect handler
     let (pending_remote, pending_receiver) = PendingRemote::<dyn MathService>::new_pipe().unwrap();
-    let _remote = pending_remote.bind_with_options(None, Some(Box::new(move || (quit_loop)())));
+    let _remote = pending_remote.bind_with_options(None, Some(Box::new(quit_loop)));
     drop(pending_receiver);
 
     run_loop.run();
diff --git a/net/cert/x509_util_unittest.cc b/net/cert/x509_util_unittest.cc
index faab5ea..92c3dd0f 100644
--- a/net/cert/x509_util_unittest.cc
+++ b/net/cert/x509_util_unittest.cc
@@ -781,8 +781,8 @@
   }
   {
     // Last component is too big.
-    const uint8_t oid[] = {0x1, 0x2, 0x3, 0x82, 0x0, 0x0, 0x0,
-                           0x0, 0x0, 0x0, 0x0,  0x0, 0x0};
+    const uint8_t oid[] = {0x1,  0x2,  0x3,  0x82, 0x80, 0x80, 0x80,
+                           0x80, 0x80, 0x80, 0x80, 0x80, 0x0};
     auto last = LastOidComponentFromBase(oid, base);
     EXPECT_EQ(last, std::nullopt);
   }
diff --git a/remoting/base/BUILD.gn b/remoting/base/BUILD.gn
index 0af1540..bd25e510 100644
--- a/remoting/base/BUILD.gn
+++ b/remoting/base/BUILD.gn
@@ -46,6 +46,8 @@
     "auto_thread.h",
     "auto_thread_task_runner.cc",
     "auto_thread_task_runner.h",
+    "branding.cc",
+    "branding.h",
     "buffered_socket_writer.cc",
     "buffered_socket_writer.h",
     "capabilities.cc",
@@ -135,6 +137,8 @@
     "url_loader_network_service_observer.h",
     "url_request_context_getter.cc",
     "url_request_context_getter.h",
+    "username.cc",
+    "username.h",
     "util.cc",
     "util.h",
     "vlog_net_log.cc",
diff --git a/remoting/base/branding.cc b/remoting/base/branding.cc
new file mode 100644
index 0000000..c3beac2
--- /dev/null
+++ b/remoting/base/branding.cc
@@ -0,0 +1,87 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/base/branding.h"
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "build/build_config.h"
+
+#if BUILDFLAG(IS_LINUX)
+#include <unistd.h>
+
+#include "remoting/base/file_path_util_linux.h"
+#include "remoting/base/username.h"
+#endif
+
+namespace {
+
+// TODO(lambroslambrou): The default locations should depend on whether Chrome
+// branding is enabled - this means also modifying the Python daemon script.
+// The actual location of the files is ultimately determined by the service
+// daemon and native messaging host - these defaults are only used in case the
+// command-line switches are absent.
+#if BUILDFLAG(IS_WIN)
+#ifdef OFFICIAL_BUILD
+const base::FilePath::CharType kConfigDir[] =
+    FILE_PATH_LITERAL("Google\\Chrome Remote Desktop");
+#else
+const base::FilePath::CharType kConfigDir[] = FILE_PATH_LITERAL("Chromoting");
+#endif
+#elif BUILDFLAG(IS_APPLE)
+const base::FilePath::CharType kConfigDir[] =
+    FILE_PATH_LITERAL("Chrome Remote Desktop");
+#elif !BUILDFLAG(IS_LINUX)
+const base::FilePath::CharType kConfigDir[] =
+    FILE_PATH_LITERAL(".config/chrome-remote-desktop");
+#endif
+
+#if !BUILDFLAG(IS_LINUX)
+base::FilePath GetConfigDirWithPrefix(int prefix_path_key) {
+  base::FilePath app_data_dir;
+  base::PathService::Get(prefix_path_key, &app_data_dir);
+  if (app_data_dir.empty()) {
+    LOG(ERROR) << "Failed to get path for key: " << prefix_path_key;
+    return {};
+  }
+  return app_data_dir.Append(kConfigDir);
+}
+#endif
+
+}  // namespace
+
+namespace remoting {
+
+#if BUILDFLAG(IS_WIN)
+const wchar_t kWindowsServiceName[] = L"chromoting";
+#endif
+
+base::FilePath GetConfigDir() {
+#if BUILDFLAG(IS_LINUX)
+  if (getuid() == /*root*/ 0 || GetUsername() == GetNetworkProcessUsername()) {
+    // Processes run as root:
+    //     daemon process,
+    //     elevated native messaging host (for managing multi-process host)
+    // Processes run as network user: network process
+    return GetMultiProcessHostGlobalConfigDir();
+  } else {
+    // Other processes:
+    //     single-process host,
+    //     desktop process,
+    //     user launched processes (e.g. remoting-webauthn),
+    //     unelevated native messaging host (for managing single-process host)
+    return GetPerUserConfigDir();
+  }
+#elif BUILDFLAG(IS_WIN)
+  return GetConfigDirWithPrefix(base::DIR_COMMON_APP_DATA);
+#elif BUILDFLAG(IS_APPLE)
+  return GetConfigDirWithPrefix(base::DIR_APP_DATA);
+#else
+  return GetConfigDirWithPrefix(base::DIR_HOME);
+#endif
+}
+
+}  // namespace remoting
diff --git a/remoting/base/branding.h b/remoting/base/branding.h
new file mode 100644
index 0000000..93822ff
--- /dev/null
+++ b/remoting/base/branding.h
@@ -0,0 +1,24 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_BASE_BRANDING_H_
+#define REMOTING_BASE_BRANDING_H_
+
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+
+namespace remoting {
+
+#if BUILDFLAG(IS_WIN)
+// Windows chromoting service name.
+extern const wchar_t kWindowsServiceName[];
+#endif
+
+// Returns the a directory for storing chromoting config files. Depending on the
+// platform, different users may get different config directories.
+base::FilePath GetConfigDir();
+
+}  // namespace remoting
+
+#endif  // REMOTING_BASE_BRANDING_H_
diff --git a/remoting/base/crash/crashpad_database_manager.cc b/remoting/base/crash/crashpad_database_manager.cc
index 2839ffe..8564f47 100644
--- a/remoting/base/crash/crashpad_database_manager.cc
+++ b/remoting/base/crash/crashpad_database_manager.cc
@@ -13,6 +13,7 @@
 #include "base/i18n/time_formatting.h"
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
+#include "remoting/base/branding.h"
 #include "remoting/base/file_path_util_linux.h"
 #include "third_party/crashpad/crashpad/client/crash_report_database.h"
 #include "third_party/crashpad/crashpad/client/settings.h"
@@ -48,7 +49,9 @@
 #if BUILDFLAG(IS_WIN)
   base::PathService::Get(base::BasePathKey::DIR_ASSETS, &database_path);
 #else
-  database_path = GetConfigDirectoryPath();
+  // TODO: crbug.com/475611769 - fix multi-process Linux host. The current
+  // implementation will create one crash database per Linux user.
+  database_path = GetConfigDir();
 #endif
   return database_path.Append(kChromotingCrashpadDatabasePath);
 }
diff --git a/remoting/base/file_host_settings_linux.cc b/remoting/base/file_host_settings_linux.cc
index 5da7a4da..87d0edd 100644
--- a/remoting/base/file_host_settings_linux.cc
+++ b/remoting/base/file_host_settings_linux.cc
@@ -4,13 +4,14 @@
 
 #include "remoting/base/file_host_settings.h"
 
+#include "remoting/base/branding.h"
 #include "remoting/base/file_path_util_linux.h"
 
 namespace remoting {
 
 base::FilePath FileHostSettings::GetSettingsFilePath() {
-  return (base::FilePath(
-      GetConfigDirectoryPath().Append(GetHostHash() + ".settings.json")));
+  return (
+      base::FilePath(GetConfigDir().Append(GetHostHash() + ".settings.json")));
 }
 
 }  // namespace remoting
diff --git a/remoting/base/file_path_util_linux.cc b/remoting/base/file_path_util_linux.cc
index 5b17b36..b24cb3e 100644
--- a/remoting/base/file_path_util_linux.cc
+++ b/remoting/base/file_path_util_linux.cc
@@ -4,7 +4,8 @@
 
 #include "remoting/base/file_path_util_linux.h"
 
-#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
@@ -12,16 +13,22 @@
 #include "net/base/network_interfaces.h"
 
 namespace remoting {
-
-base::FilePath GetConfigDirectoryPath() {
-  base::FilePath homedir;
-  base::PathService::Get(base::DIR_HOME, &homedir);
-  return homedir.Append(".config/chrome-remote-desktop");
-}
+namespace {
+const base::FilePath::CharType kConfigDir[] =
+    FILE_PATH_LITERAL("chrome-remote-desktop");
+}  // namespace
 
 std::string GetHostHash() {
   return "host#" +
          base::HexEncodeLower(crypto::obsolete::Md5::Hash(net::GetHostName()));
 }
 
+base::FilePath GetMultiProcessHostGlobalConfigDir() {
+  return base::FilePath("/etc").Append(kConfigDir);
+}
+
+base::FilePath GetPerUserConfigDir() {
+  return base::GetHomeDir().Append(".config").Append(kConfigDir);
+}
+
 }  // namespace remoting
diff --git a/remoting/base/file_path_util_linux.h b/remoting/base/file_path_util_linux.h
index a986b45..5ee8df8 100644
--- a/remoting/base/file_path_util_linux.h
+++ b/remoting/base/file_path_util_linux.h
@@ -11,13 +11,26 @@
 
 namespace remoting {
 
-// Returns the path to the directory that store host configurations.
-base::FilePath GetConfigDirectoryPath();
-
 // Returns a string that can be used to construct a host config file name, e.g.
 // "host#1234567890aabbccddeeff1234567890".
+// DEPRECATED: This should only be used for the single-process host for
+// compatibility reasons. New config/setting files should not have the host hash
+// in the filename.
 std::string GetHostHash();
 
+// Returns the directory where the host config file for the multi-process host
+// is located. Note that only processes run as root will have access to files in
+// the directory.
+base::FilePath GetMultiProcessHostGlobalConfigDir();
+
+// Returns the per-user chromoting config directory.
+// On the single-process host, this is where the host config file is located,
+// i.e. this is what `GetConfigDir()` returns.
+// On the multi-process host, this will return a path in the home directory of
+// the user that the process is run as, so this is generally only useful for the
+// desktop process, which is always run as the login user.
+base::FilePath GetPerUserConfigDir();
+
 }  // namespace remoting
 
 #endif  // REMOTING_BASE_FILE_PATH_UTIL_LINUX_H_
diff --git a/remoting/host/base/username.cc b/remoting/base/username.cc
similarity index 88%
rename from remoting/host/base/username.cc
rename to remoting/base/username.cc
index f783b19..f7e5cb0 100644
--- a/remoting/host/base/username.cc
+++ b/remoting/base/username.cc
@@ -1,13 +1,14 @@
-// Copyright 2013 The Chromium Authors
+// Copyright 2026 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "remoting/host/base/username.h"
+#include "remoting/base/username.h"
 
 #include <vector>
 
 #include "base/logging.h"
 #include "base/notimplemented.h"
+#include "base/strings/cstring_view.h"
 #include "build/build_config.h"
 
 #if BUILDFLAG(IS_POSIX)
@@ -85,4 +86,14 @@
 #endif  // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID)
 }
 
+#if BUILDFLAG(IS_LINUX)
+
+base::cstring_view GetNetworkProcessUsername() {
+  // Should be in sync with CRD_NETWORK_USER in
+  // //remoting/host/installer/linux/debian/postinst
+  return "_crd_network";
+}
+
+#endif  // BUILDFLAG(IS_LINUX)
+
 }  // namespace remoting
diff --git a/remoting/base/username.h b/remoting/base/username.h
new file mode 100644
index 0000000..2843966
--- /dev/null
+++ b/remoting/base/username.h
@@ -0,0 +1,26 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_BASE_USERNAME_H_
+#define REMOTING_BASE_USERNAME_H_
+
+#include <string>
+
+#include "base/strings/cstring_view.h"
+#include "build/build_config.h"
+
+namespace remoting {
+
+// Returns the username associated with this process, or the empty string on
+// error or if not implemented.
+std::string GetUsername();
+
+#if BUILDFLAG(IS_LINUX)
+// Returns the username that the network process is run as.
+base::cstring_view GetNetworkProcessUsername();
+#endif  // BUILDFLAG(IS_LINUX)
+
+}  // namespace remoting
+
+#endif  // REMOTING_BASE_USERNAME_H_
diff --git a/remoting/codec/audio_encoder_opus.cc b/remoting/codec/audio_encoder_opus.cc
index 1167daa..249cd73 100644
--- a/remoting/codec/audio_encoder_opus.cc
+++ b/remoting/codec/audio_encoder_opus.cc
@@ -28,6 +28,11 @@
 constexpr AudioPacket::SamplingRate kOpusSamplingRate =
     AudioPacket::SAMPLING_RATE_48000;
 
+// If not using `kOpusSampleRate`, the only other input sample rate we accept is
+// 44.1kHz.
+constexpr AudioPacket::SamplingRate kAltSamplingRate =
+    AudioPacket::SAMPLING_RATE_44100;
+
 // Opus supports frame sizes of 2.5, 5, 10, 20, 40 and 60 ms. We use 20 ms
 // frames to balance latency and efficiency.
 constexpr base::TimeDelta kFrameDuration = base::Milliseconds(20);
@@ -40,18 +45,122 @@
 constexpr AudioPacket::BytesPerSample kBytesPerSample =
     AudioPacket::BYTES_PER_SAMPLE_2;
 
+// Size in frames of the resampler's fill requests.
+constexpr size_t kResamplerRequestSize =
+    media::SincResampler::kDefaultRequestSize;
+
 constexpr bool IsSupportedSampleRate(int rate) {
   return rate == 44100 || rate == 48000;
 }
 
 }  // namespace
 
+class ResamplerFifoImpl : public AudioEncoderOpus::ResamplerFifo {
+ public:
+  explicit ResamplerFifoImpl(size_t size_in_frames, size_t channels)
+      : chunk_size_(kResamplerRequestSize * channels),
+        storage_buffer_(
+            base::AlignedUninit<int16_t>(size_in_frames * channels,
+                                         media::AudioBus::kChannelAlignment)),
+        crossover_buffer_(
+            base::AlignedUninit<int16_t>(chunk_size_,
+                                         media::AudioBus::kChannelAlignment)) {}
+  ~ResamplerFifoImpl() override = default;
+
+  void AddNewSamples(base::span<const int16_t> samples) override {
+    CHECK(new_samples_.empty());
+    new_samples_ = samples;
+  }
+
+  [[nodiscard]] base::span<const int16_t> TakeChunk() override {
+    CHECK_GE(remaining_samples(), chunk_size_);
+
+    if (saved_samples_.empty()) {
+      return new_samples_.take_first(chunk_size_);
+    }
+
+    if (saved_samples_.size() > chunk_size_) {
+      return saved_samples_.take_first(chunk_size_);
+    }
+
+    // If we're here, we have to combine some of the previous samples with the
+    // new ones. To return a continuous span, we have to make a temporary copy
+    // into `crossover_buffer_`.
+    const size_t new_samples_needed = chunk_size_ - saved_samples_.size();
+    auto [saved_dest, new_dest] =
+        base::span(crossover_buffer_).split_at(saved_samples_.size());
+
+    saved_dest.copy_from_nonoverlapping(saved_samples_);
+    saved_samples_ = {};
+
+    new_dest.copy_from_nonoverlapping(
+        new_samples_.take_first(new_samples_needed));
+
+    // Return merged samples.
+    return crossover_buffer_;
+  }
+
+  void SaveNewSamples() override {
+    CHECK_LE(remaining_samples(), storage_buffer_.size());
+
+    // No other saved samples. Copy directly.
+    if (saved_samples_.empty()) {
+      auto save_dest = storage_buffer_.first(new_samples_.size());
+      save_dest.copy_from_nonoverlapping(new_samples_);
+      new_samples_ = {};
+      saved_samples_ = save_dest;
+      return;
+    }
+
+    // There are some saved samples remaining. Copy them to the front of
+    // `storage_buffer_` first, and then append the new samples.
+    const size_t total_size = remaining_samples();
+    auto storage = storage_buffer_.first(total_size);
+    auto [previous_dest, new_dest] = storage.split_at(saved_samples_.size());
+
+    previous_dest.copy_from(saved_samples_);
+    new_dest.copy_from_nonoverlapping(new_samples_);
+
+    new_samples_ = {};
+    saved_samples_ = storage;
+  }
+
+  size_t remaining_samples() const override {
+    return saved_samples_.size() + new_samples_.size();
+  }
+
+  size_t GetChunkSizeForTesting() const override { return chunk_size_; }
+
+ private:
+  const size_t chunk_size_;
+
+  // Location where `new_samples_` are copied, during `SaveNewSamples()`;
+  base::AlignedHeapArray<int16_t> storage_buffer_;
+
+  // Temporary location where `saved_samples_` and `new_samples_` are stored,
+  // when there aren't enough saved samples to completely fill one chunk.
+  base::AlignedHeapArray<int16_t> crossover_buffer_;
+
+  // Portion of `storage_buffer_` which contains unused samples.
+  base::raw_span<const int16_t> saved_samples_;
+
+  // Points towards external, unowned memory.
+  base::raw_span<const int16_t> new_samples_;
+};
+
 AudioEncoderOpus::AudioEncoderOpus() = default;
 
 AudioEncoderOpus::~AudioEncoderOpus() {
   DestroyEncoder();
 }
 
+// static
+std::unique_ptr<AudioEncoderOpus::ResamplerFifo>
+AudioEncoderOpus::GetEmptyFifoForTesting(size_t size_in_frames,
+                                         size_t channels) {
+  return std::make_unique<ResamplerFifoImpl>(size_in_frames, channels);
+}
+
 void AudioEncoderOpus::InitEncoder() {
   DCHECK(!encoder_);
   int error;
@@ -64,30 +173,31 @@
 
   opus_encoder_ctl(encoder_.get(), OPUS_SET_BITRATE(kOutputBitrateBps));
 
-  frame_size_ =
-      media::AudioTimestampHelper::TimeToFrames(kFrameDuration, sampling_rate_);
+  needs_resampling_ = sampling_rate_ != kOpusSamplingRate;
 
-  if (sampling_rate_ != kOpusSamplingRate) {
-    size_t total_samples =
-        base::CheckMul(kOpusFrameCount, channels_).ValueOrDie<size_t>();
-    resample_buffer_ = base::AlignedUninit<int16_t>(
-        total_samples, media::AudioBus::kChannelAlignment);
-    // TODO(sergeyu): Figure out the right buffer size to use per packet instead
-    // of using media::SincResampler::kDefaultRequestSize.
+  // Drop any previous samples.
+  leftover_encoder_samples_ = {};
+
+  if (needs_resampling_) {
+    CHECK_EQ(sampling_rate_, kAltSamplingRate);
     resampler_ = std::make_unique<media::MultiChannelResampler>(
         channels_, sampling_rate_ / double{kOpusSamplingRate},
-        media::SincResampler::kDefaultRequestSize,
+        kResamplerRequestSize,
         base::BindRepeating(&AudioEncoderOpus::FetchBytesToResample,
                             base::Unretained(this)));
+
+    const size_t min_input_frames_needed =
+        resampler_->GetMaxInputFramesRequested(kOpusFrameCount);
+
+    resampling_samples_needed_ = min_input_frames_needed * channels_;
+
+    resampler_fifo_ = std::make_unique<ResamplerFifoImpl>(
+        resampling_samples_needed_, channels_);
     resampler_bus_ = media::AudioBus::Create(channels_, kOpusFrameCount);
   }
 
-  // Drop leftover data because it's for different sampling rate.
-  leftover_frames_ = 0;
-  leftover_samples_size_in_frames_ =
-      frame_size_ + media::SincResampler::kDefaultRequestSize;
-  leftover_samples_.reset(
-      new int16_t[leftover_samples_size_in_frames_ * channels_]);
+  encoder_samples_needed_ = kOpusFrameCount * channels_;
+  encoder_input_ = base::AlignedUninit<int16_t>(encoder_samples_needed_);
 }
 
 void AudioEncoderOpus::DestroyEncoder() {
@@ -123,17 +233,12 @@
 
 void AudioEncoderOpus::FetchBytesToResample(int resampler_frame_delay,
                                             media::AudioBus* audio_bus) {
-  DCHECK(resampling_data_);
-  int samples_left = (resampling_data_size_ - resampling_data_pos_) /
-                     kBytesPerSample / channels_;
-  DCHECK_LE(audio_bus->frames(), samples_left);
+  CHECK(needs_resampling_);
+  CHECK_GE(resampler_fifo_->remaining_samples(),
+           static_cast<size_t>(audio_bus->frames() * channels_));
   static_assert(kBytesPerSample == 2, "FromInterleaved expects 2 bytes.");
   audio_bus->FromInterleaved<media::SignedInt16SampleTypeTraits>(
-      reinterpret_cast<const int16_t*>(
-          UNSAFE_TODO(resampling_data_ + resampling_data_pos_)),
-      audio_bus->frames());
-  resampling_data_pos_ += audio_bus->frames() * kBytesPerSample * channels_;
-  DCHECK_LE(resampling_data_pos_, static_cast<int>(resampling_data_size_));
+      resampler_fifo_->TakeChunk());
 }
 
 int AudioEncoderOpus::GetBitrate() {
@@ -151,90 +256,91 @@
     return nullptr;
   }
 
-  int frames_in_packet = packet->data(0).size() / kBytesPerSample / channels_;
-  const int16_t* next_sample =
-      UNSAFE_TODO(reinterpret_cast<const int16_t*>(packet->data(0).data()));
+  base::span<const uint8_t> byte_input = base::as_byte_span(packet->data(0));
+  CHECK_EQ(byte_input.size() % kBytesPerSample, 0u);
+  CHECK(base::IsAligned(byte_input.data(), sizeof(int16_t)));
 
+  // SAFETY: This data is coming from an external source, but we've CHECK'ed
+  // that there are the right number of bytes, and that they have the proper
+  // alignment.
+  base::span<const int16_t> input_samples = UNSAFE_BUFFERS(
+      base::span(reinterpret_cast<const int16_t*>(byte_input.data()),
+                 byte_input.size() / sizeof(int16_t)));
+
+  if (needs_resampling_) {
+    return EncodeInternalWithResampling(input_samples);
+  }
+
+  return EncodeInternal(input_samples);
+}
+
+bool AudioEncoderOpus::EncodeData(base::span<const int16_t> samples,
+                                  AudioPacket* destination) {
+  CHECK_EQ(samples.size(), encoder_samples_needed_);
+
+  // Initialize output buffer.
+  std::string* data = destination->add_data();
+  data->resize(encoder_samples_needed_ * kBytesPerSample);
+
+  // Encode.
+  unsigned char* buffer = reinterpret_cast<unsigned char*>(std::data(*data));
+  int result = opus_encode(encoder_, samples.data(), kOpusFrameCount, buffer,
+                           data->length());
+  if (result < 0) {
+    LOG(ERROR) << "opus_encode() failed with error code: " << result;
+    return false;
+  }
+
+  CHECK_LE(result, static_cast<int>(data->length()));
+  data->resize(result);
+  return true;
+}
+
+std::unique_ptr<AudioPacket> AudioEncoderOpus::CreatePacket() {
   // Create a new packet of encoded data.
   auto encoded_packet = std::make_unique<AudioPacket>();
   encoded_packet->set_encoding(AudioPacket::ENCODING_OPUS);
   encoded_packet->set_sampling_rate(kOpusSamplingRate);
   encoded_packet->set_channels(channels_);
+  return encoded_packet;
+}
 
-  const int prefetch_frames =
-      resampler_.get() ? media::SincResampler::kDefaultRequestSize : 0;
-  int frames_wanted = frame_size_ + prefetch_frames;
+std::unique_ptr<AudioPacket> AudioEncoderOpus::EncodeInternal(
+    base::span<const int16_t> input_samples) {
+  CHECK(!needs_resampling_);
 
-  while (leftover_frames_ + frames_in_packet >= frames_wanted) {
-    const int16_t* pcm_buffer = nullptr;
+  // Create a new packet of encoded data.
+  auto encoded_packet = CreatePacket();
 
-    // Combine the packet with the leftover samples, if any.
-    if (leftover_frames_ > 0) {
-      pcm_buffer = leftover_samples_.get();
-      const int frames_to_copy = frames_wanted - leftover_frames_;
-      UNSAFE_TODO(memcpy(leftover_samples_.get() + leftover_frames_ * channels_,
-                         next_sample,
-                         frames_to_copy * kBytesPerSample * channels_));
-    } else {
-      pcm_buffer = next_sample;
+  while (leftover_encoder_samples_.size() + input_samples.size() >=
+         encoder_samples_needed_) {
+    // If there are no leftover frames, encode directly.
+    if (leftover_encoder_samples_.empty()) {
+      EncodeData(input_samples.take_first(encoder_samples_needed_),
+                 encoded_packet.get());
+      continue;
     }
 
-    // Resample data if necessary.
-    int frames_consumed = 0;
-    if (resampler_.get()) {
-      resampling_data_ = reinterpret_cast<const char*>(pcm_buffer);
-      resampling_data_pos_ = 0;
-      resampling_data_size_ = frames_wanted * channels_ * kBytesPerSample;
-      resampler_->Resample(kOpusFrameCount, resampler_bus_.get());
-      resampling_data_ = nullptr;
-      frames_consumed = resampling_data_pos_ / channels_ / kBytesPerSample;
+    // Fill `encoder_input_` completely.
+    const size_t free_space_size =
+        encoder_samples_needed_ - leftover_encoder_samples_.size();
 
-      static_assert(kBytesPerSample == 2, "ToInterleaved expects 2 bytes.");
-      resampler_bus_->ToInterleaved<media::SignedInt16SampleTypeTraits>(
-          resample_buffer_);
-      pcm_buffer = resample_buffer_.data();
-    } else {
-      frames_consumed = frame_size_;
-    }
+    encoder_input_.subspan(leftover_encoder_samples_.size())
+        .copy_from_nonoverlapping(input_samples.take_first(free_space_size));
 
-    // Initialize output buffer.
-    std::string* data = encoded_packet->add_data();
-    data->resize(kOpusFrameCount * kBytesPerSample * channels_);
-
-    // Encode.
-    unsigned char* buffer = reinterpret_cast<unsigned char*>(std::data(*data));
-    int result = opus_encode(encoder_, pcm_buffer, kOpusFrameCount, buffer,
-                             data->length());
-    if (result < 0) {
-      LOG(ERROR) << "opus_encode() failed with error code: " << result;
-      return nullptr;
-    }
-
-    DCHECK_LE(result, static_cast<int>(data->length()));
-    data->resize(result);
-
-    // Cleanup leftover buffer.
-    if (frames_consumed >= leftover_frames_) {
-      frames_consumed -= leftover_frames_;
-      leftover_frames_ = 0;
-      UNSAFE_TODO(next_sample += frames_consumed * channels_);
-      frames_in_packet -= frames_consumed;
-    } else {
-      leftover_frames_ -= frames_consumed;
-      UNSAFE_TODO(memmove(leftover_samples_.get(),
-                          leftover_samples_.get() + frames_consumed * channels_,
-                          leftover_frames_ * channels_ * kBytesPerSample));
-    }
+    // Encode the samples. All clean leftovers, as they already encoded.
+    EncodeData(encoder_input_, encoded_packet.get());
+    leftover_encoder_samples_ = {};
   }
 
-  // Store the leftover samples.
-  if (frames_in_packet > 0) {
-    DCHECK_LE(leftover_frames_ + frames_in_packet,
-              leftover_samples_size_in_frames_);
-    UNSAFE_TODO(memmove(leftover_samples_.get() + leftover_frames_ * channels_,
-                        next_sample,
-                        frames_in_packet * kBytesPerSample * channels_));
-    leftover_frames_ += frames_in_packet;
+  // Copy unused samples into `encoder_input_`.
+  if (!input_samples.empty()) {
+    CHECK_LT(input_samples.size(), encoder_samples_needed_);
+    const size_t used_space = leftover_encoder_samples_.size();
+    encoder_input_.subspan(used_space, input_samples.size())
+        .copy_from_nonoverlapping(input_samples);
+    leftover_encoder_samples_ =
+        encoder_input_.first(used_space + input_samples.size());
   }
 
   // Return nullptr if there's nothing in the packet.
@@ -245,4 +351,38 @@
   return encoded_packet;
 }
 
+std::unique_ptr<AudioPacket> AudioEncoderOpus::EncodeInternalWithResampling(
+    base::span<const int16_t> input_samples) {
+  CHECK(needs_resampling_);
+  CHECK(resampler_fifo_);
+
+  // We always encode the full `encoder_samples_needed_` samples in
+  // `encoder_input_` at once. Leftover samples are stored in `resampling_fifo_`
+  // instead, at their original `kAltSamplingRate`.
+  CHECK(leftover_encoder_samples_.empty());
+
+  // Create a new packet of encoded data.
+  auto encoded_packet = CreatePacket();
+
+  // Add a reference to the incoming samples without copying them.
+  resampler_fifo_->AddNewSamples(input_samples);
+
+  while (resampler_fifo_->remaining_samples() >= resampling_samples_needed_) {
+    resampler_->Resample(kOpusFrameCount, resampler_bus_.get());
+    resampler_bus_->ToInterleaved<media::SignedInt16SampleTypeTraits>(
+        encoder_input_);
+    EncodeData(encoder_input_, encoded_packet.get());
+  }
+
+  // Save unused samples.
+  resampler_fifo_->SaveNewSamples();
+
+  // Return nullptr if there's nothing in the packet.
+  if (encoded_packet->data_size() == 0) {
+    return nullptr;
+  }
+
+  return encoded_packet;
+}
+
 }  // namespace remoting
diff --git a/remoting/codec/audio_encoder_opus.h b/remoting/codec/audio_encoder_opus.h
index b048334..d0b90fe 100644
--- a/remoting/codec/audio_encoder_opus.h
+++ b/remoting/codec/audio_encoder_opus.h
@@ -7,6 +7,7 @@
 
 #include "base/memory/aligned_memory.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/raw_span.h"
 #include "remoting/codec/audio_encoder.h"
 #include "remoting/proto/audio.pb.h"
 
@@ -23,6 +24,28 @@
 
 class AudioEncoderOpus : public AudioEncoder {
  public:
+  // Helper class which segments samples into chunks, while minimizing copies.
+  // Exposed as an interface here for ease of testing.
+  class ResamplerFifo {
+   public:
+    virtual ~ResamplerFifo() = default;
+
+    // Add samples to the FIFO, without copying them.
+    virtual void AddNewSamples(base::span<const int16_t> samples) = 0;
+
+    // Copies unused samples added by `AddNewSamples()` to internal storage.
+    virtual void SaveNewSamples() = 0;
+
+    // Consumes samples from the FIFO.
+    virtual base::span<const int16_t> TakeChunk() = 0;
+
+    // Returns the number of samples currently in the FIFO, saved or not.
+    virtual size_t remaining_samples() const = 0;
+
+    // Returns the size of each chunk returned by `TakeChunk()`.
+    virtual size_t GetChunkSizeForTesting() const = 0;
+  };
+
   AudioEncoderOpus();
 
   AudioEncoderOpus(const AudioEncoderOpus&) = delete;
@@ -35,32 +58,53 @@
       std::unique_ptr<AudioPacket> packet) override;
   int GetBitrate() override;
 
+  static std::unique_ptr<ResamplerFifo> GetEmptyFifoForTesting(
+      size_t size_in_frames,
+      size_t channels);
+
  private:
   void InitEncoder();
   void DestroyEncoder();
   bool ResetForPacket(AudioPacket* packet);
 
+  std::unique_ptr<AudioPacket> CreatePacket();
+
+  std::unique_ptr<AudioPacket> EncodeInternal(base::span<const int16_t> data);
+  std::unique_ptr<AudioPacket> EncodeInternalWithResampling(
+      base::span<const int16_t> data);
+
   void FetchBytesToResample(int resampler_frame_delay,
                             media::AudioBus* audio_bus);
 
+  bool EncodeData(base::span<const int16_t> data, AudioPacket* destination);
+
+  bool needs_resampling_ = false;
+
+  // Holds samples (always at 48kHz) that have not yet been encoded.
+  base::AlignedHeapArray<int16_t> encoder_input_;
+
+  // The portion of `encoder_input_` which contains samples that have not yet
+  // been encoded.
+  // Unused when `needs_resampling_` is true, since extra samples will be stored
+  // in `resampler_fifo_` instead.
+  base::raw_span<int16_t> leftover_encoder_samples_;
+
+  // Number of samples needed to encode a single "Opus frame".
+  size_t encoder_samples_needed_ = 0;
+
+  // Manages samples which have not been resampled yet.
+  std::unique_ptr<ResamplerFifo> resampler_fifo_;
+
+  // The minimum number of samples needed to guarantee to have enough for
+  // one resample call.
+  size_t resampling_samples_needed_ = 0;
+
   int sampling_rate_ = 0;
   AudioPacket::Channels channels_ = AudioPacket::CHANNELS_STEREO;
   raw_ptr<OpusEncoder, DanglingUntriaged> encoder_ = nullptr;
 
-  int frame_size_ = 0;
   std::unique_ptr<media::MultiChannelResampler> resampler_;
-  base::AlignedHeapArray<int16_t> resample_buffer_;
   std::unique_ptr<media::AudioBus> resampler_bus_;
-
-  // Used to pass packet to the FetchBytesToResampler() callback.
-  const char* resampling_data_ = nullptr;
-  int resampling_data_size_ = 0;
-  int resampling_data_pos_ = 0;
-
-  // Left-over unencoded samples from the previous AudioPacket.
-  std::unique_ptr<int16_t[]> leftover_samples_;
-  int leftover_samples_size_in_frames_ = 0;
-  int leftover_frames_ = 0;
 };
 
 }  // namespace remoting
diff --git a/remoting/codec/audio_encoder_opus_unittest.cc b/remoting/codec/audio_encoder_opus_unittest.cc
index 0e50ab86..2a242aad 100644
--- a/remoting/codec/audio_encoder_opus_unittest.cc
+++ b/remoting/codec/audio_encoder_opus_unittest.cc
@@ -24,8 +24,6 @@
 // Maximum value that can be encoded in a 16-bit signed sample.
 const int kMaxSampleValue = 32767;
 
-const int kChannels = 2;
-
 // Phase shift between left and right channels.
 const double kChannelPhaseShift = 2 * std::numbers::pi / 3;
 
@@ -71,31 +69,34 @@
   std::unique_ptr<AudioPacket> CreatePacket(int samples,
                                             AudioPacket::SamplingRate rate,
                                             double frequency_hz,
-                                            int pos) {
-    std::vector<int16_t> data(samples * kChannels);
+                                            int pos,
+                                            int channels) {
+    std::vector<int16_t> data(samples * channels);
     for (int i = 0; i < samples; ++i) {
-      data[i * kChannels] = GetSampleValue(rate, frequency_hz, i + pos, 0);
-      data[i * kChannels + 1] = GetSampleValue(rate, frequency_hz, i + pos, 1);
+      for (int j = 0; j < channels; ++j) {
+        data[i * channels + j] = GetSampleValue(rate, frequency_hz, i + pos, j);
+      }
     }
 
     std::unique_ptr<AudioPacket> packet(new AudioPacket());
-    packet->add_data(reinterpret_cast<char*>(&(data[0])),
-                     samples * kChannels * sizeof(int16_t));
+    packet->add_data(reinterpret_cast<char*>(data.data()),
+                     samples * channels * sizeof(int16_t));
     packet->set_encoding(AudioPacket::ENCODING_RAW);
     packet->set_sampling_rate(rate);
     packet->set_bytes_per_sample(AudioPacket::BYTES_PER_SAMPLE_2);
-    packet->set_channels(AudioPacket::CHANNELS_STEREO);
+    packet->set_channels(static_cast<AudioPacket::Channels>(channels));
     return packet;
   }
 
   // Decoded data is normally shifted in phase relative to the original signal.
   // This function returns the approximate shift in samples by finding the first
   // point when signal goes from negative to positive.
-  double EstimateSignalShift(const std::vector<int16_t>& received_data) {
+  double EstimateSignalShift(const std::vector<int16_t>& received_data,
+                             int channels) {
     for (size_t i = kSkippedFirstSamples;
-         i < received_data.size() / kChannels - 1; i++) {
-      int16_t this_sample = received_data[i * kChannels];
-      int16_t next_sample = received_data[(i + 1) * kChannels];
+         i < received_data.size() / channels - 1; i++) {
+      int16_t this_sample = received_data[i * channels];
+      int16_t next_sample = received_data[(i + 1) * channels];
       if (this_sample < 0 && next_sample > 0) {
         return i +
                static_cast<double>(-this_sample) / (next_sample - this_sample);
@@ -110,17 +111,17 @@
   void ValidateReceivedData(int samples,
                             AudioPacket::SamplingRate rate,
                             double frequency_hz,
-                            const std::vector<int16_t>& received_data) {
-    double shift = EstimateSignalShift(received_data);
+                            const std::vector<int16_t>& received_data,
+                            int channels) {
+    double shift = EstimateSignalShift(received_data, channels);
     double diff_sqare_sum = 0;
-    for (size_t i = kSkippedFirstSamples; i < received_data.size() / kChannels;
+    for (size_t i = kSkippedFirstSamples; i < received_data.size() / channels;
          i++) {
-      double d = received_data[i * kChannels] -
-                 GetSampleValue(rate, frequency_hz, i - shift, 0);
-      diff_sqare_sum += d * d;
-      d = received_data[i * kChannels + 1] -
-          GetSampleValue(rate, frequency_hz, i - shift, 1);
-      diff_sqare_sum += d * d;
+      for (int j = 0; j < channels; ++j) {
+        double d = received_data[i * channels + j] -
+                   GetSampleValue(rate, frequency_hz, i - shift, j);
+        diff_sqare_sum += d * d;
+      }
     }
     double deviation =
         std::sqrt(diff_sqare_sum / received_data.size()) / kMaxSampleValue;
@@ -130,7 +131,8 @@
 
   void TestEncodeDecode(int packet_size,
                         double frequency_hz,
-                        AudioPacket::SamplingRate rate) {
+                        AudioPacket::SamplingRate rate,
+                        int channels = 2) {
     const int kTotalTestSamples = 24000;
 
     encoder_ = std::make_unique<AudioEncoderOpus>();
@@ -140,7 +142,7 @@
     int pos = 0;
     for (; pos < kTotalTestSamples; pos += packet_size) {
       std::unique_ptr<AudioPacket> source_packet =
-          CreatePacket(packet_size, rate, frequency_hz, pos);
+          CreatePacket(packet_size, rate, frequency_hz, pos, channels);
       std::unique_ptr<AudioPacket> encoded =
           encoder_->Encode(std::move(source_packet));
       if (encoded.get()) {
@@ -159,11 +161,11 @@
 
     // Verify that at most kMaxLatencyMs worth of samples is buffered inside
     // |encoder_| and |decoder_|.
-    EXPECT_GE(static_cast<int>(received_data.size()) / kChannels,
+    EXPECT_GE(static_cast<int>(received_data.size()) / channels,
               pos - rate * kMaxLatencyMs / 1000);
 
     ValidateReceivedData(packet_size, kDefaultSamplingRate, frequency_hz,
-                         received_data);
+                         received_data, channels);
   }
 
  protected:
@@ -191,4 +193,207 @@
   TestEncodeDecode(5000, 3000, AudioPacket::SAMPLING_RATE_44100);
 }
 
+TEST_F(OpusAudioEncoderTest, Mono) {
+  TestEncodeDecode(2000, 3000, AudioPacket::SAMPLING_RATE_48000, 1);
+}
+
+TEST_F(OpusAudioEncoderTest, DynamicConfigChange) {
+  encoder_ = std::make_unique<AudioEncoderOpus>();
+  decoder_ = std::make_unique<AudioDecoderOpus>();
+
+  auto test_config = [&](int samples, AudioPacket::SamplingRate rate,
+                         int channels) {
+    std::unique_ptr<AudioPacket> source_packet =
+        CreatePacket(samples, rate, 3000, 0, channels);
+    std::unique_ptr<AudioPacket> encoded =
+        encoder_->Encode(std::move(source_packet));
+    // It might take multiple packets to get output due to buffering,
+    // but here we just want to ensure it doesn't crash and eventually
+    // produces something or handles the reset.
+    if (encoded) {
+      std::unique_ptr<AudioPacket> decoded =
+          decoder_->Decode(std::move(encoded));
+      EXPECT_EQ(kDefaultSamplingRate, decoded->sampling_rate());
+      EXPECT_EQ(channels, decoded->channels());
+    }
+  };
+
+  // Switch between various configs.
+  test_config(2000, AudioPacket::SAMPLING_RATE_48000, 2);
+  test_config(2000, AudioPacket::SAMPLING_RATE_44100, 2);
+  test_config(2000, AudioPacket::SAMPLING_RATE_48000, 1);
+  test_config(2000, AudioPacket::SAMPLING_RATE_44100, 1);
+}
+
+TEST_F(OpusAudioEncoderTest, UnsupportedParameters) {
+  encoder_ = std::make_unique<AudioEncoderOpus>();
+
+  // 3 channels (Unsupported).
+  auto packet =
+      CreatePacket(2000, AudioPacket::SAMPLING_RATE_48000, 3000, 0, 3);
+  EXPECT_FALSE(encoder_->Encode(std::move(packet)));
+
+  // Unsupported sampling rate.
+  packet = CreatePacket(2000, static_cast<AudioPacket::SamplingRate>(8000),
+                        3000, 0, 2);
+  EXPECT_FALSE(encoder_->Encode(std::move(packet)));
+}
+
+TEST_F(OpusAudioEncoderTest, SmallPackets) {
+  encoder_ = std::make_unique<AudioEncoderOpus>();
+
+  // Send 10ms of 48kHz audio (480 samples). Opus frame is 20ms.
+  auto packet = CreatePacket(480, AudioPacket::SAMPLING_RATE_48000, 3000, 0, 2);
+  auto encoded = encoder_->Encode(std::move(packet));
+  EXPECT_FALSE(encoded);  // Should be buffered.
+
+  // Send another 10ms.
+  packet = CreatePacket(480, AudioPacket::SAMPLING_RATE_48000, 3000, 480, 2);
+  encoded = encoder_->Encode(std::move(packet));
+  EXPECT_TRUE(encoded);  // Now we should have a full 20ms frame.
+}
+
+TEST_F(OpusAudioEncoderTest, SmallPacketsWithResampling) {
+  encoder_ = std::make_unique<AudioEncoderOpus>();
+
+  // Send 10ms of 44.1kHz audio (441 samples). Opus frame is 20ms.
+  auto packet = CreatePacket(441, AudioPacket::SAMPLING_RATE_44100, 3000, 0, 2);
+  auto encoded = encoder_->Encode(std::move(packet));
+  EXPECT_FALSE(encoded);  // Should be buffered.
+
+  // Send another 30ms (1323 samples). Total 40ms.
+  packet = CreatePacket(1323, AudioPacket::SAMPLING_RATE_44100, 3000, 441, 2);
+  encoded = encoder_->Encode(std::move(packet));
+  EXPECT_TRUE(encoded);
+  // We should have at least one encoded chunk.
+  EXPECT_GE(encoded->data_size(), 1);
+}
+
+TEST_F(OpusAudioEncoderTest, GetBitrate) {
+  encoder_ = std::make_unique<AudioEncoderOpus>();
+  EXPECT_EQ(encoder_->GetBitrate(), 160 * 1024);
+}
+
+// Makes sure that the FIFO returns spans from the new samples, without copying
+// data.
+TEST_F(OpusAudioEncoderTest, ResamplerFifo_TakeChunkDirect) {
+  auto fifo = AudioEncoderOpus::GetEmptyFifoForTesting(2048, 2);
+  ASSERT_TRUE(fifo);
+  const size_t chunk_size = fifo->GetChunkSizeForTesting();
+
+  std::vector<int16_t> samples(chunk_size, 42);
+  fifo->AddNewSamples(samples);
+  EXPECT_EQ(fifo->remaining_samples(), chunk_size);
+
+  auto chunk = fifo->TakeChunk();
+  // Make sure we haven't copied any data internally.
+  EXPECT_EQ(chunk.data(), samples.data());
+
+  EXPECT_EQ(chunk.size(), chunk_size);
+  EXPECT_EQ(chunk[0], 42);
+  EXPECT_EQ(fifo->remaining_samples(), 0u);
+
+  fifo.reset();
+}
+
+// Make sure that `SaveNewSamples()` copies new samples to internal storage.
+TEST_F(OpusAudioEncoderTest, ResamplerFifo_SaveNewSamples) {
+  auto fifo = AudioEncoderOpus::GetEmptyFifoForTesting(2048, 2);
+  const size_t chunk_size = fifo->GetChunkSizeForTesting();
+
+  {
+    std::vector<int16_t> data = {1, 2, 3, 4};
+    fifo->AddNewSamples(data);
+    EXPECT_EQ(fifo->remaining_samples(), 4u);
+
+    // This copies `data` into the FIFO's internal storage.
+    fifo->SaveNewSamples();
+  }
+  // `data` is now destroyed.
+
+  EXPECT_EQ(fifo->remaining_samples(), 4u);
+
+  // Verify the data is still there by completing a chunk and taking it.
+  std::vector<int16_t> part2(chunk_size - 4, 7);
+  fifo->AddNewSamples(part2);
+  auto chunk = fifo->TakeChunk();
+  EXPECT_EQ(chunk.size(), chunk_size);
+  EXPECT_EQ(chunk[0], 1);
+  EXPECT_EQ(chunk[3], 4);
+  EXPECT_EQ(chunk[4], 7);
+
+  fifo.reset();
+}
+
+// Make sure that we can receive a mix of saved and new samples.
+TEST_F(OpusAudioEncoderTest, ResamplerFifo_TakeChunkCrossover) {
+  auto fifo = AudioEncoderOpus::GetEmptyFifoForTesting(2048, 2);
+  const size_t chunk_size = fifo->GetChunkSizeForTesting();
+
+  // Add some samples and compact.
+  std::vector<int16_t> samples_to_save = {1, 2, 3, 4};
+  fifo->AddNewSamples(samples_to_save);
+  fifo->SaveNewSamples();
+
+  // Add more samples to complete a chunk.
+  std::vector<int16_t> large_chunks(chunk_size * 2, 7);
+  fifo->AddNewSamples(large_chunks);
+
+  // TakeChunk should now use the crossover buffer.
+  auto chunk = fifo->TakeChunk();
+  EXPECT_EQ(chunk.size(), chunk_size);
+  EXPECT_EQ(chunk[0], 1);
+  EXPECT_EQ(chunk[3], 4);
+  EXPECT_EQ(chunk[4], 7);
+
+  // Make sure we can pull remaining samples from the remaining `large_chunks`.
+  auto chunk_from_new_samples = fifo->TakeChunk();
+  EXPECT_EQ(chunk_from_new_samples.size(), chunk_size);
+
+  fifo.reset();
+}
+
+TEST_F(OpusAudioEncoderTest, ResamplerFifo_TakeChunkFromSavedSamples) {
+  auto fifo = AudioEncoderOpus::GetEmptyFifoForTesting(2048, 2);
+  const size_t chunk_size = fifo->GetChunkSizeForTesting();
+
+  // Add more than a chunk and compact.
+  std::vector<int16_t> data(chunk_size + 10, 42);
+  fifo->AddNewSamples(data);
+  fifo->SaveNewSamples();
+
+  // TakeChunk should pull from the compacted buffer.
+  auto chunk = fifo->TakeChunk();
+  EXPECT_EQ(chunk.size(), chunk_size);
+  EXPECT_EQ(fifo->remaining_samples(), 10u);
+
+  fifo.reset();
+}
+
+// Makes sure that
+TEST_F(OpusAudioEncoderTest, ResamplerFifo_SaveLargeChunks) {
+  const size_t kFifoSize = 2048;
+  auto fifo = AudioEncoderOpus::GetEmptyFifoForTesting(kFifoSize, 2);
+
+  // Completely fill the FIFO.
+  std::vector<int16_t> max_capacity(kFifoSize, 42);
+  fifo->AddNewSamples(max_capacity);
+  fifo->SaveNewSamples();
+
+  // Add more samples that can fit
+  std::vector<int16_t> huge_chunk(kFifoSize * 2, 42);
+  fifo->AddNewSamples(huge_chunk);
+
+  EXPECT_GT(fifo->remaining_samples(), kFifoSize);
+
+  while (fifo->remaining_samples() > kFifoSize) {
+    std::ignore = fifo->TakeChunk();
+  }
+
+  // Make sure we can save the data after consuming enough of it.
+  fifo->SaveNewSamples();
+
+  fifo.reset();
+}
+
 }  // namespace remoting
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index 99b46ae..7c11604 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -154,6 +154,7 @@
     "//base",
     "//components/named_mojo_ipc_server",
     "//mojo/public/cpp/platform",
+    "//remoting/base",
     "//remoting/host/base",
   ]
 }
@@ -342,8 +343,6 @@
     "backoff_timer.cc",
     "backoff_timer.h",
     "basic_desktop_environment.cc",
-    "branding.cc",
-    "branding.h",
     "chromoting_host.cc",
     "chromoting_host.h",
     "chromoting_host_context.cc",
diff --git a/remoting/host/base/BUILD.gn b/remoting/host/base/BUILD.gn
index eb702a8..da85642c 100644
--- a/remoting/host/base/BUILD.gn
+++ b/remoting/host/base/BUILD.gn
@@ -20,7 +20,6 @@
     "screen_controls.h",
     "screen_resolution.h",
     "switches.h",
-    "username.h",
   ]
 
   sources = [
@@ -30,7 +29,6 @@
     "process_util.cc",
     "screen_resolution.cc",
     "switches.cc",
-    "username.cc",
   ]
 
   public_deps = [ "//remoting/base:logging" ]
diff --git a/remoting/host/base/username.h b/remoting/host/base/username.h
deleted file mode 100644
index 207dd45..0000000
--- a/remoting/host/base/username.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2013 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef REMOTING_HOST_BASE_USERNAME_H_
-#define REMOTING_HOST_BASE_USERNAME_H_
-
-#include <string>
-
-namespace remoting {
-
-// Returns the username associated with this process, or the empty string on
-// error or if not implemented.
-std::string GetUsername();
-
-}  // namespace remoting
-
-#endif  // REMOTING_HOST_BASE_USERNAME_H_
diff --git a/remoting/host/branding.cc b/remoting/host/branding.cc
deleted file mode 100644
index 69f7b7ad..0000000
--- a/remoting/host/branding.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2012 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "remoting/host/branding.h"
-
-#include "base/base_paths.h"
-#include "base/path_service.h"
-#include "build/build_config.h"
-
-namespace {
-
-// TODO(lambroslambrou): The default locations should depend on whether Chrome
-// branding is enabled - this means also modifying the Python daemon script.
-// The actual location of the files is ultimately determined by the service
-// daemon and native messaging host - these defaults are only used in case the
-// command-line switches are absent.
-#if BUILDFLAG(IS_WIN)
-#ifdef OFFICIAL_BUILD
-const base::FilePath::CharType kConfigDir[] =
-    FILE_PATH_LITERAL("Google\\Chrome Remote Desktop");
-#else
-const base::FilePath::CharType kConfigDir[] = FILE_PATH_LITERAL("Chromoting");
-#endif
-#elif BUILDFLAG(IS_APPLE)
-const base::FilePath::CharType kConfigDir[] =
-    FILE_PATH_LITERAL("Chrome Remote Desktop");
-#else
-const base::FilePath::CharType kConfigDir[] =
-    FILE_PATH_LITERAL(".config/chrome-remote-desktop");
-#endif
-
-}  // namespace
-
-namespace remoting {
-
-#if BUILDFLAG(IS_WIN)
-const wchar_t kWindowsServiceName[] = L"chromoting";
-#endif
-
-base::FilePath GetConfigDir() {
-  base::FilePath app_data_dir;
-
-#if BUILDFLAG(IS_WIN)
-  base::PathService::Get(base::DIR_COMMON_APP_DATA, &app_data_dir);
-#elif BUILDFLAG(IS_APPLE)
-  base::PathService::Get(base::DIR_APP_DATA, &app_data_dir);
-#else
-  base::PathService::Get(base::DIR_HOME, &app_data_dir);
-#endif
-
-  return app_data_dir.Append(kConfigDir);
-}
-
-}  // namespace remoting
diff --git a/remoting/host/branding.h b/remoting/host/branding.h
deleted file mode 100644
index 694592a0..0000000
--- a/remoting/host/branding.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2012 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef REMOTING_HOST_BRANDING_H_
-#define REMOTING_HOST_BRANDING_H_
-
-#include "base/files/file_path.h"
-#include "build/build_config.h"
-
-namespace remoting {
-
-#if BUILDFLAG(IS_WIN)
-// Windows chromoting service name.
-extern const wchar_t kWindowsServiceName[];
-#endif
-
-// Returns the location of the host configuration directory.
-base::FilePath GetConfigDir();
-
-}  // namespace remoting
-
-#endif  // REMOTING_HOST_BRANDING_H_
diff --git a/remoting/host/daemon_process.cc b/remoting/host/daemon_process.cc
index a3d5399..bad55b4 100644
--- a/remoting/host/daemon_process.cc
+++ b/remoting/host/daemon_process.cc
@@ -19,10 +19,10 @@
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "remoting/base/auto_thread_task_runner.h"
+#include "remoting/base/branding.h"
 #include "remoting/base/constants.h"
 #include "remoting/host/base/host_exit_codes.h"
 #include "remoting/host/base/screen_resolution.h"
-#include "remoting/host/branding.h"
 #include "remoting/host/config_file_watcher.h"
 #include "remoting/host/desktop_session.h"
 #include "remoting/host/host_event_logger.h"
diff --git a/remoting/host/daemon_process_linux.cc b/remoting/host/daemon_process_linux.cc
index 0f8defb..8e72590 100644
--- a/remoting/host/daemon_process_linux.cc
+++ b/remoting/host/daemon_process_linux.cc
@@ -34,12 +34,13 @@
 #include "mojo/public/cpp/system/message_pipe.h"
 #include "remoting/base/auto_thread.h"
 #include "remoting/base/auto_thread_task_runner.h"
+#include "remoting/base/branding.h"
 #include "remoting/base/crash/crash_reporting_breakpad.h"
 #include "remoting/base/logging.h"
+#include "remoting/base/username.h"
 #include "remoting/host/base/host_exit_codes.h"
 #include "remoting/host/base/screen_resolution.h"
 #include "remoting/host/base/switches.h"
-#include "remoting/host/branding.h"
 #include "remoting/host/chromoting_host_services_server.h"
 #include "remoting/host/host_config.h"
 #include "remoting/host/host_main.h"
@@ -282,6 +283,9 @@
   auto daemon_process = std::make_unique<DaemonProcessLinux>(
       caller_task_runner, io_task_runner, std::move(stopped_callback));
 
+  // TODO: crbug.com/475611769 - set ACL on the pairing registry directory for
+  // the network user.
+
   daemon_process->StartDesktopSessionFactory();
 
   // Finishes configuring the Daemon process and launches the network process.
diff --git a/remoting/host/daemon_process_win.cc b/remoting/host/daemon_process_win.cc
index 9b4396d8..edab598f 100644
--- a/remoting/host/daemon_process_win.cc
+++ b/remoting/host/daemon_process_win.cc
@@ -35,13 +35,13 @@
 #include "mojo/public/cpp/system/message_pipe.h"
 #include "remoting/base/auto_thread.h"
 #include "remoting/base/auto_thread_task_runner.h"
+#include "remoting/base/branding.h"
 #include "remoting/base/crash/crash_reporting_breakpad.h"
 #include "remoting/base/logging.h"
 #include "remoting/base/scoped_sc_handle_win.h"
 #include "remoting/host/base/host_exit_codes.h"
 #include "remoting/host/base/screen_resolution.h"
 #include "remoting/host/base/switches.h"
-#include "remoting/host/branding.h"
 #include "remoting/host/chromoting_host_services_server.h"
 #include "remoting/host/crash/minidump_handler.h"
 #include "remoting/host/desktop_session_win.h"
diff --git a/remoting/host/ipc_constants.cc b/remoting/host/ipc_constants.cc
index e20d5ad..935e03e7 100644
--- a/remoting/host/ipc_constants.cc
+++ b/remoting/host/ipc_constants.cc
@@ -11,7 +11,7 @@
 #include "build/build_config.h"
 #include "components/named_mojo_ipc_server/named_mojo_ipc_util.h"
 #include "mojo/public/cpp/platform/named_platform_channel.h"
-#include "remoting/host/base/username.h"
+#include "remoting/base/username.h"
 
 namespace remoting {
 
@@ -139,12 +139,6 @@
   return *server_name;
 }
 
-base::cstring_view GetNetworkProcessUsername() {
-  // Should be in sync with CRD_NETWORK_USER in
-  // //remoting/host/installer/linux/debian/postinst
-  return "_crd_network";
-}
-
 #endif  // BUILDFLAG(IS_LINUX)
 
 }  // namespace remoting
diff --git a/remoting/host/ipc_constants.h b/remoting/host/ipc_constants.h
index b1604af7..31c3693 100644
--- a/remoting/host/ipc_constants.h
+++ b/remoting/host/ipc_constants.h
@@ -46,9 +46,6 @@
 // Returns the server name for the login session reporter.
 const mojo::NamedPlatformChannel::ServerName&
 GetLoginSessionReporterServerName();
-
-// Returns the username that the network process is run as.
-base::cstring_view GetNetworkProcessUsername();
 #endif  // BUILDFLAG(IS_LINUX)
 
 }  // namespace remoting
diff --git a/remoting/host/linux/gnome_remote_desktop_session.cc b/remoting/host/linux/gnome_remote_desktop_session.cc
index 0e37787..9f78905 100644
--- a/remoting/host/linux/gnome_remote_desktop_session.cc
+++ b/remoting/host/linux/gnome_remote_desktop_session.cc
@@ -19,6 +19,7 @@
 #include "base/strings/string_split.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/types/expected.h"
+#include "remoting/base/branding.h"
 #include "remoting/base/file_path_util_linux.h"
 #include "remoting/base/logging.h"
 #include "remoting/host/base/switches.h"
@@ -44,7 +45,7 @@
 
 base::FilePath GetDisplayLayoutFilePath() {
   return (base::FilePath(
-      GetConfigDirectoryPath().Append(GetHostHash() + ".display_layout.pb")));
+      GetConfigDir().Append(GetHostHash() + ".display_layout.pb")));
 }
 
 std::unique_ptr<protocol::VideoLayout> CreateDefaultLayout() {
diff --git a/remoting/host/mac/host_service_main.cc b/remoting/host/mac/host_service_main.cc
index 2ea07d3..b9f6ad5 100644
--- a/remoting/host/mac/host_service_main.cc
+++ b/remoting/host/mac/host_service_main.cc
@@ -26,9 +26,9 @@
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "remoting/base/logging.h"
+#include "remoting/base/username.h"
 #include "remoting/host/base/host_exit_codes.h"
 #include "remoting/host/base/switches.h"
-#include "remoting/host/base/username.h"
 #include "remoting/host/mac/constants_mac.h"
 #include "remoting/host/version.h"
 
diff --git a/remoting/host/pairing_registry_delegate_linux.cc b/remoting/host/pairing_registry_delegate_linux.cc
index 4e1d3c8..f11252e 100644
--- a/remoting/host/pairing_registry_delegate_linux.cc
+++ b/remoting/host/pairing_registry_delegate_linux.cc
@@ -20,7 +20,7 @@
 #include "base/logging.h"
 #include "base/strings/stringprintf.h"
 #include "base/values.h"
-#include "remoting/host/branding.h"
+#include "remoting/base/branding.h"
 
 namespace {
 
diff --git a/remoting/host/pam_authorization_factory_posix.cc b/remoting/host/pam_authorization_factory_posix.cc
index 6f70a87..109f8c1 100644
--- a/remoting/host/pam_authorization_factory_posix.cc
+++ b/remoting/host/pam_authorization_factory_posix.cc
@@ -14,7 +14,7 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "remoting/base/logging.h"
-#include "remoting/host/base/username.h"
+#include "remoting/base/username.h"
 #include "remoting/host/pam_utils.h"
 #include "remoting/protocol/channel_authenticator.h"
 
diff --git a/remoting/host/remoting_me2me_host.cc b/remoting/host/remoting_me2me_host.cc
index 4c323cce..24eefc7 100644
--- a/remoting/host/remoting_me2me_host.cc
+++ b/remoting/host/remoting_me2me_host.cc
@@ -54,6 +54,7 @@
 #include "net/base/network_change_notifier.h"
 #include "remoting/base/authentication_method.h"
 #include "remoting/base/auto_thread_task_runner.h"
+#include "remoting/base/branding.h"
 #include "remoting/base/cloud_session_authz_service_client_factory.h"
 #include "remoting/base/corp_session_authz_service_client_factory.h"
 #include "remoting/base/cpu_utils.h"
@@ -69,12 +70,11 @@
 #include "remoting/base/rsa_key_pair.h"
 #include "remoting/base/service_urls.h"
 #include "remoting/base/session_policies.h"
+#include "remoting/base/username.h"
 #include "remoting/host/base/desktop_environment_options.h"
 #include "remoting/host/base/host_exit_codes.h"
 #include "remoting/host/base/switches.h"
-#include "remoting/host/base/username.h"
 #include "remoting/host/basic_desktop_environment.h"
-#include "remoting/host/branding.h"
 #include "remoting/host/chromoting_host.h"
 #include "remoting/host/chromoting_host_context.h"
 #include "remoting/host/cloud_heartbeat_service_client.h"
diff --git a/remoting/host/setup/daemon_controller_delegate_linux.cc b/remoting/host/setup/daemon_controller_delegate_linux.cc
index 7612609..15147d5 100644
--- a/remoting/host/setup/daemon_controller_delegate_linux.cc
+++ b/remoting/host/setup/daemon_controller_delegate_linux.cc
@@ -26,6 +26,7 @@
 #include "base/strings/string_util.h"
 #include "base/values.h"
 #include "build/build_config.h"
+#include "remoting/base/branding.h"
 #include "remoting/base/file_path_util_linux.h"
 #include "remoting/host/host_config.h"
 #include "remoting/host/usage_stats_consent.h"
@@ -53,7 +54,7 @@
     return current_process->GetSwitchValuePath(kHostConfigSwitchName);
   }
   std::string filename = GetHostHash() + ".json";
-  return GetConfigDirectoryPath().Append(filename);
+  return GetConfigDir().Append(filename);
 }
 
 bool GetScriptPath(base::FilePath* result) {
diff --git a/remoting/host/setup/daemon_controller_delegate_win.cc b/remoting/host/setup/daemon_controller_delegate_win.cc
index 4c3912e..10be7a2 100644
--- a/remoting/host/setup/daemon_controller_delegate_win.cc
+++ b/remoting/host/setup/daemon_controller_delegate_win.cc
@@ -18,9 +18,9 @@
 #include "base/memory/ptr_util.h"
 #include "base/values.h"
 #include "base/win/scoped_bstr.h"
+#include "remoting/base/branding.h"
 #include "remoting/base/is_google_email.h"
 #include "remoting/base/scoped_sc_handle_win.h"
-#include "remoting/host/branding.h"
 #include "remoting/host/host_config.h"
 #include "remoting/host/usage_stats_consent.h"
 #include "remoting/host/win/security_descriptor.h"
diff --git a/remoting/host/usage_stats_consent_linux.cc b/remoting/host/usage_stats_consent_linux.cc
index a14efd1..d335acf4 100644
--- a/remoting/host/usage_stats_consent_linux.cc
+++ b/remoting/host/usage_stats_consent_linux.cc
@@ -13,6 +13,7 @@
 #include "base/logging.h"
 #include "base/notimplemented.h"
 #include "base/values.h"
+#include "remoting/base/branding.h"
 #include "remoting/base/file_path_util_linux.h"
 #include "remoting/base/is_google_email.h"
 #include "remoting/host/config_file_watcher.h"
@@ -25,7 +26,7 @@
   *allowed = false;
 
   std::string filename = GetHostHash() + ".json";
-  base::FilePath config_path = GetConfigDirectoryPath().Append(filename);
+  base::FilePath config_path = GetConfigDir().Append(filename);
   std::optional<base::DictValue> config(HostConfigFromJsonFile(config_path));
   if (!config.has_value()) {
     LOG(ERROR) << "No host config file found.";
diff --git a/remoting/host/win/host_service.cc b/remoting/host/win/host_service.cc
index 9a58ebb..af144fdc 100644
--- a/remoting/host/win/host_service.cc
+++ b/remoting/host/win/host_service.cc
@@ -27,11 +27,11 @@
 #include "base/win/message_window.h"
 #include "base/win/scoped_com_initializer.h"
 #include "remoting/base/auto_thread.h"
+#include "remoting/base/branding.h"
 #include "remoting/base/cpu_utils.h"
 #include "remoting/base/logging.h"
 #include "remoting/base/scoped_sc_handle_win.h"
 #include "remoting/host/base/host_exit_codes.h"
-#include "remoting/host/branding.h"
 #include "remoting/host/daemon_process.h"
 #include "remoting/host/win/com_security.h"
 #include "remoting/host/win/core_resource.h"
diff --git a/sandbox/win/src/parallel_launch_test.cc b/sandbox/win/src/parallel_launch_test.cc
index ed091be..3b39718 100644
--- a/sandbox/win/src/parallel_launch_test.cc
+++ b/sandbox/win/src/parallel_launch_test.cc
@@ -86,7 +86,8 @@
   static_cast<BrokerServicesBase*>(broker)->SetBrokerServicesDelegateForTesting(
       std::unique_ptr<BrokerServicesDelegate>(delegate));
 
-  base::CommandLine cmd_line = sandbox::CreateCommandLineForTesting("wait");
+  base::CommandLine cmd_line =
+      WaitCommandTestRunner::CreateCommandLineForTesting();
 
   auto policy = broker->CreatePolicy();
   EXPECT_EQ(SBOX_ALL_OK, policy->GetConfig()->SetTokenLevel(USER_INTERACTIVE,
@@ -159,7 +160,8 @@
   static_cast<BrokerServicesBase*>(broker)->SetBrokerServicesDelegateForTesting(
       std::unique_ptr<BrokerServicesDelegate>(delegate));
 
-  base::CommandLine cmd_line = sandbox::CreateCommandLineForTesting("wait");
+  base::CommandLine cmd_line =
+      WaitCommandTestRunner::CreateCommandLineForTesting();
 
   base::RunLoop run_loop;
   int launches_remaining_count = 2;
diff --git a/sandbox/win/src/policy_target_test.cc b/sandbox/win/src/policy_target_test.cc
index d2bfd5c7..e3e2312 100644
--- a/sandbox/win/src/policy_target_test.cc
+++ b/sandbox/win/src/policy_target_test.cc
@@ -6,34 +6,87 @@
 
 #include <string_view>
 
-#include "base/compiler_specific.h"
 #include "base/environment.h"
 #include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/writable_shared_memory_region.h"
 #include "base/scoped_environment_variable_override.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/strings/string_util.h"
-#include "base/test/task_environment.h"
-#include "base/test/test_future.h"
-#include "base/win/scoped_process_information.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/security_descriptor.h"
 #include "base/win/windows_handle_util.h"
 #include "sandbox/win/src/broker_services.h"
 #include "sandbox/win/src/sandbox.h"
 #include "sandbox/win/src/sandbox_factory.h"
 #include "sandbox/win/src/sandbox_policy.h"
 #include "sandbox/win/src/target_services.h"
+#include "sandbox/win/src/window.h"
 #include "sandbox/win/tests/common/controller.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace sandbox {
 
+namespace {
+
+SBOX_TEST_COMMAND(SharedMemoryCommand) {
+  if (args.size() < 2) {
+    return SBOX_TEST_FIRST_ERROR;
+  }
+  size_t raw_handle;
+  if (!base::StringToSizeT(args[0], &raw_handle)) {
+    return SBOX_TEST_SECOND_ERROR;
+  }
+  // First extract the handle to the platform-native ScopedHandle.
+  base::win::ScopedHandle scoped_handle(reinterpret_cast<HANDLE>(raw_handle));
+  if (!scoped_handle.is_valid()) {
+    return SBOX_TEST_THIRD_ERROR;
+  }
+
+  auto test_contents = base::as_byte_span(args[1]);
+  // Then convert to the low-level chromium region.
+  base::subtle::PlatformSharedMemoryRegion platform_region =
+      base::subtle::PlatformSharedMemoryRegion::Take(
+          std::move(scoped_handle),
+          base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly,
+          test_contents.size(), base::UnguessableToken::Create());
+  // Finally wrap the low-level region in the shared memory API.
+  base::ReadOnlySharedMemoryRegion region =
+      base::ReadOnlySharedMemoryRegion::Deserialize(std::move(platform_region));
+  if (!region.IsValid()) {
+    return SBOX_TEST_FOURTH_ERROR;
+  }
+  base::ReadOnlySharedMemoryMapping view = region.Map();
+  if (!view.IsValid()) {
+    return SBOX_TEST_FIFTH_ERROR;
+  }
+  auto contents = base::span(view);
+  if (contents != test_contents) {
+    return SBOX_TEST_SIXTH_ERROR;
+  }
+  return SBOX_TEST_SUCCEEDED;
+}
+
+// Tests that environment is filtered correctly.
+SBOX_TEST_COMMAND(FilterEnvironmentCommand) {
+  auto env = base::Environment::Create();
+  // "TMP" should never be filtered. See `CreateFilteredEnvironment`.
+  if (!env->HasVar("TMP")) {
+    return SBOX_TEST_FIRST_ERROR;
+  }
+  if (env->HasVar("SBOX_TEST_ENV")) {
+    return SBOX_TEST_SECOND_ERROR;
+  }
+  return SBOX_TEST_SUCCEEDED;
+}
+
+}  // namespace
+
 #define BINDNTDLL(name)                                   \
   name##Function name = reinterpret_cast<name##Function>( \
       ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), #name))
 
 // Reverts to self and verify that SetInformationToken was faked. Returns
 // SBOX_TEST_SUCCEEDED if faked and SBOX_TEST_FAILED if not faked.
-SBOX_TESTS_COMMAND int PolicyTargetTest_token(int argc, wchar_t** argv) {
+SBOX_TEST_COMMAND(OpenTokenCommand) {
   HANDLE thread_token;
   // Get the thread token, using impersonation.
   if (!::OpenThreadToken(GetCurrentThread(),
@@ -56,7 +109,7 @@
 // Stores the high privilege token on a static variable, change impersonation
 // again to that one and verify that we are not interfering anymore with
 // RevertToSelf.
-SBOX_TESTS_COMMAND int PolicyTargetTest_steal(int argc, wchar_t** argv) {
+SBOX_TEST_COMMAND(StealTokenCommand) {
   static HANDLE thread_token;
   if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) {
     if (!::OpenThreadToken(GetCurrentThread(),
@@ -68,7 +121,7 @@
       return ::GetLastError();
 
     // See if we fake the call again.
-    int ret = PolicyTargetTest_token(argc, argv);
+    int ret = OpenTokenCommandImpl(args);
     ::CloseHandle(thread_token);
     return ret;
   }
@@ -76,344 +129,253 @@
 }
 
 // Opens the thread token with and without impersonation.
-SBOX_TESTS_COMMAND int PolicyTargetTest_token2(int argc, wchar_t** argv) {
+SBOX_TEST_COMMAND(OpenToken2Command) {
   HANDLE thread_token;
   // Get the thread token, using impersonation.
   if (!::OpenThreadToken(GetCurrentThread(),
                          TOKEN_IMPERSONATE | TOKEN_DUPLICATE, false,
-                         &thread_token))
+                         &thread_token)) {
     return ::GetLastError();
+  }
   ::CloseHandle(thread_token);
 
   // Get the thread token, without impersonation.
   if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_DUPLICATE,
-                       true, &thread_token))
+                       true, &thread_token)) {
     return ::GetLastError();
+  }
   ::CloseHandle(thread_token);
   return SBOX_TEST_SUCCEEDED;
 }
 
 // Opens the thread token with and without impersonation, using
 // NtOpenThreadTokenEX.
-SBOX_TESTS_COMMAND int PolicyTargetTest_token3(int argc, wchar_t** argv) {
+SBOX_TEST_COMMAND(OpenToken3Command) {
   BINDNTDLL(NtOpenThreadTokenEx);
-  if (!NtOpenThreadTokenEx)
+  if (!NtOpenThreadTokenEx) {
     return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
-
+  }
   HANDLE thread_token;
   // Get the thread token, using impersonation.
   NTSTATUS status = NtOpenThreadTokenEx(GetCurrentThread(),
                                         TOKEN_IMPERSONATE | TOKEN_DUPLICATE,
                                         false, 0, &thread_token);
-  if (status == STATUS_NO_TOKEN)
+  if (status == STATUS_NO_TOKEN) {
     return ERROR_NO_TOKEN;
-  if (!NT_SUCCESS(status))
+  }
+  if (!NT_SUCCESS(status)) {
     return SBOX_TEST_FAILED;
-
+  }
   ::CloseHandle(thread_token);
 
   // Get the thread token, without impersonation.
   status = NtOpenThreadTokenEx(GetCurrentThread(),
                                TOKEN_IMPERSONATE | TOKEN_DUPLICATE, true, 0,
                                &thread_token);
-  if (!NT_SUCCESS(status))
+  if (!NT_SUCCESS(status)) {
     return SBOX_TEST_FAILED;
-
+  }
   ::CloseHandle(thread_token);
   return SBOX_TEST_SUCCEEDED;
 }
 
 // Tests that we can open the current thread.
-SBOX_TESTS_COMMAND int PolicyTargetTest_thread(int argc, wchar_t** argv) {
+SBOX_TEST_COMMAND(OpenThreadCommand) {
   DWORD thread_id = ::GetCurrentThreadId();
-  HANDLE thread = ::OpenThread(SYNCHRONIZE, false, thread_id);
-  if (!thread)
-    return ::GetLastError();
-  if (!::CloseHandle(thread))
-    return ::GetLastError();
-
-  return SBOX_TEST_SUCCEEDED;
+  base::win::ScopedHandle thread(::OpenThread(SYNCHRONIZE, false, thread_id));
+  return thread.is_valid() ? SBOX_TEST_SUCCEEDED : ::GetLastError();
 }
 
 // New thread entry point: do  nothing.
 DWORD WINAPI PolicyTargetTest_thread_main(void* param) {
-  ::Sleep(INFINITE);
   return 0;
 }
 
 // Tests that we can create a new thread, and open it.
-SBOX_TESTS_COMMAND int PolicyTargetTest_thread2(int argc, wchar_t** argv) {
+SBOX_TEST_COMMAND(CreateThreadCommand) {
   // Use default values to create a new thread.
   DWORD thread_id;
-  HANDLE thread = ::CreateThread(nullptr, 0, &PolicyTargetTest_thread_main, 0,
-                                 0, &thread_id);
-  if (!thread)
+  base::win::ScopedHandle thread(::CreateThread(
+      nullptr, 0, &PolicyTargetTest_thread_main, 0, 0, &thread_id));
+  if (!thread.is_valid()) {
     return ::GetLastError();
-  if (!::CloseHandle(thread))
-    return ::GetLastError();
+  }
 
-  thread = ::OpenThread(SYNCHRONIZE, false, thread_id);
-  if (!thread)
-    return ::GetLastError();
-
-  if (!::CloseHandle(thread))
-    return ::GetLastError();
-
-  return SBOX_TEST_SUCCEEDED;
+  base::win::ScopedHandle thread2(::OpenThread(SYNCHRONIZE, false, thread_id));
+  return thread2.is_valid() ? SBOX_TEST_SUCCEEDED : ::GetLastError();
 }
 
 // Tests that we can call CreateProcess.
-SBOX_TESTS_COMMAND int PolicyTargetTest_process(int argc, wchar_t** argv) {
+SBOX_TEST_COMMAND(CreateProcessCommand) {
   // Use default values to create a new process.
-  STARTUPINFO startup_info = {0};
-  startup_info.cb = sizeof(startup_info);
-  PROCESS_INFORMATION temp_process_info = {};
+  STARTUPINFO startup_info = {};
+  PROCESS_INFORMATION proc_info = {};
   // Note: CreateProcessW() can write to its lpCommandLine, don't pass a
   // raw string literal.
-  std::wstring writable_cmdline_str(L"foo.exe");
-  if (!::CreateProcessW(L"foo.exe", &writable_cmdline_str[0], nullptr, nullptr,
+  std::wstring cmd_line(L"foo.exe");
+  if (!::CreateProcessW(L"foo.exe", std::data(cmd_line), nullptr, nullptr,
                         false, 0, nullptr, nullptr, &startup_info,
-                        &temp_process_info))
+                        &proc_info)) {
     return SBOX_TEST_SUCCEEDED;
-  base::win::ScopedProcessInformation process_info(temp_process_info);
+  }
+  ::CloseHandle(proc_info.hProcess);
+  ::CloseHandle(proc_info.hThread);
   return SBOX_TEST_FAILED;
 }
 
-// Tests that environment is filtered correctly.
-SBOX_TESTS_COMMAND int PolicyTargetTest_filterEnvironment(int argc,
-                                                          wchar_t** argv) {
-  auto env = base::Environment::Create();
-  // "TMP" should never be filtered. See `CreateFilteredEnvironment`.
-  if (!env->HasVar("TMP")) {
+SBOX_TEST_COMMAND(CheckDesktopNameCommand) {
+  if (args.empty()) {
     return SBOX_TEST_FIRST_ERROR;
   }
-  if (env->HasVar("SBOX_TEST_ENV")) {
+
+  HDESK desktop = ::GetThreadDesktop(::GetCurrentThreadId());
+  if (!desktop) {
     return SBOX_TEST_SECOND_ERROR;
   }
+
+  HWINSTA winsta =
+      args[0].contains(L'\\') ? ::GetProcessWindowStation() : nullptr;
+
+  if (GetFullDesktopName(winsta, desktop) != args[0]) {
+    return SBOX_TEST_THIRD_ERROR;
+  }
   return SBOX_TEST_SUCCEEDED;
 }
 
 TEST(PolicyTargetTest, SetInformationThread) {
-  TestRunner runner;
+  OpenTokenCommandTestRunner runner;
   runner.SetTestState(BEFORE_REVERT);
-  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token"));
+  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest());
 
-  TestRunner runner1;
+  OpenTokenCommandTestRunner runner1;
   runner1.SetTestState(AFTER_REVERT);
-  EXPECT_EQ(ERROR_NO_TOKEN, runner1.RunTest(L"PolicyTargetTest_token"));
+  EXPECT_EQ(ERROR_NO_TOKEN, runner1.RunTest());
 
-  TestRunner runner2;
+  StealTokenCommandTestRunner runner2;
   runner2.SetTestState(EVERY_STATE);
-  EXPECT_EQ(SBOX_TEST_FAILED, runner2.RunTest(L"PolicyTargetTest_steal"));
+  EXPECT_EQ(SBOX_TEST_FAILED, runner2.RunTest());
 }
 
 TEST(PolicyTargetTest, OpenThreadToken) {
-  TestRunner runner;
+  OpenToken2CommandTestRunner runner;
   runner.SetTestState(BEFORE_REVERT);
-  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token2"));
+  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest());
 
-  TestRunner runner2;
+  OpenToken2CommandTestRunner runner2;
   runner2.SetTestState(AFTER_REVERT);
-  EXPECT_EQ(ERROR_NO_TOKEN, runner2.RunTest(L"PolicyTargetTest_token2"));
+  EXPECT_EQ(ERROR_NO_TOKEN, runner2.RunTest());
 }
 
 TEST(PolicyTargetTest, OpenThreadTokenEx) {
-  TestRunner runner;
+  OpenToken3CommandTestRunner runner;
   runner.SetTestState(BEFORE_REVERT);
-  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token3"));
+  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest());
 
-  TestRunner runner2;
+  OpenToken3CommandTestRunner runner2;
   runner2.SetTestState(AFTER_REVERT);
-  EXPECT_EQ(ERROR_NO_TOKEN, runner2.RunTest(L"PolicyTargetTest_token3"));
+  EXPECT_EQ(ERROR_NO_TOKEN, runner2.RunTest());
 }
 
 TEST(PolicyTargetTest, OpenThread) {
-  TestRunner runner;
-  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread"))
+  OpenThreadCommandTestRunner runner;
+  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest())
       << "Opens the current thread";
 
-  TestRunner runner2;
-  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner2.RunTest(L"PolicyTargetTest_thread2"))
+  CreateThreadCommandTestRunner runner2;
+  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner2.RunTest())
       << "Creates a new thread and opens it";
 }
 
 TEST(PolicyTargetTest, OpenProcess) {
-  TestRunner runner;
-  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_process"))
-      << "Opens a process";
+  CreateProcessCommandTestRunner runner;
+  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest()) << "Opens a process";
 }
 
 // Sets the desktop for the current thread to be one with a null DACL, then
 // launches a sandboxed app. Validates that the sandboxed app has access to the
 // desktop.
 TEST(PolicyTargetTest, InheritedDesktopPolicy) {
-  base::test::TaskEnvironment task_environment;
   // Create a desktop with a null dacl - which should allow access to
   // everything.
   SECURITY_ATTRIBUTES attributes = {};
   attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
-  SECURITY_DESCRIPTOR security_desc = {};
-  ::InitializeSecurityDescriptor(&security_desc, SECURITY_DESCRIPTOR_REVISION);
-  ::SetSecurityDescriptorDacl(&security_desc, true, nullptr, false);
+  base::win::SecurityDescriptor sd;
+  sd.set_dacl(*base::win::AccessControlList::FromPACL(nullptr));
+  SECURITY_DESCRIPTOR security_desc = sd.ToAbsolute();
   attributes.lpSecurityDescriptor = &security_desc;
-  HDESK null_dacl_desktop_handle = CreateDesktop(
+  HDESK null_dacl_desktop_handle = ::CreateDesktop(
       L"null_dacl_desktop", nullptr, nullptr, 0, GENERIC_ALL, &attributes);
   EXPECT_TRUE(null_dacl_desktop_handle);
 
+  BrokerServices* broker = GetBroker();
+  ASSERT_TRUE(broker);
   // Switch to the null dacl desktop and run the test.
   HDESK old_desktop = ::GetThreadDesktop(::GetCurrentThreadId());
   EXPECT_TRUE(null_dacl_desktop_handle);
   EXPECT_TRUE(::SetThreadDesktop(null_dacl_desktop_handle));
 
-  BrokerServices* broker = GetBroker();
-
   // Precreate the desktop.
   EXPECT_EQ(SBOX_ALL_OK,
             broker->CreateAlternateDesktop(Desktop::kAlternateDesktop));
-
-  ASSERT_TRUE(broker);
-
-  base::CommandLine cmd_line = sandbox::CreateCommandLineForTesting("wait");
-
-  // Launch the app.
-  ResultCode result = SBOX_ALL_OK;
-  DWORD last_error = ERROR_SUCCESS;
-  base::win::ScopedProcessInformation target;
-
-  auto policy = broker->CreatePolicy();
-  policy->GetConfig()->SetDesktop(Desktop::kAlternateDesktop);
-  EXPECT_EQ(SBOX_ALL_OK, policy->GetConfig()->SetTokenLevel(USER_INTERACTIVE,
-                                                            USER_LOCKDOWN));
-  base::test::TestFuture<base::win::ScopedProcessInformation, DWORD, ResultCode>
-      test_future;
-  broker->SpawnTargetAsync(cmd_line, std::move(policy),
-                           test_future.GetCallback());
-  std::tie(target, last_error, result) = test_future.Take();
-  EXPECT_EQ(SBOX_ALL_OK, result);
-
-  // Run the process for some time to make sure it doesn't crash on launch
-  EXPECT_EQ(1u, ::ResumeThread(target.thread_handle()));
-  EXPECT_EQ(static_cast<DWORD>(WAIT_TIMEOUT),
-            ::WaitForSingleObject(target.process_handle(), 2000));
-
-  EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0));
-  ::WaitForSingleObject(target.process_handle(), INFINITE);
-
-  // Close the desktop handle.
-  broker->DestroyDesktops();
-
   // Close the null dacl desktop.
   EXPECT_TRUE(::SetThreadDesktop(old_desktop));
   EXPECT_TRUE(::CloseDesktop(null_dacl_desktop_handle));
+
+  CheckDesktopNameCommandTestRunner runner;
+  runner.SetTestState(BEFORE_INIT);
+  runner.GetConfig()->SetDesktop(Desktop::kAlternateDesktop);
+  std::wstring desktop_name =
+      broker->GetDesktopName(Desktop::kAlternateDesktop);
+  EXPECT_EQ(runner.RunTest(desktop_name), SBOX_TEST_SUCCEEDED);
+
+  // Close the desktop handle.
+  broker->DestroyDesktops();
 }
 
-// Launches the app in the sandbox and ask it to wait in an
-// infinite loop. Waits for 2 seconds and then check if the
-// desktop associated with the app thread is not the same as the
-// current desktop.
+// Launches the app in the sandbox and check it was assigned the alternative
+// desktop on the current window station.
 TEST(PolicyTargetTest, DesktopPolicy) {
-  base::test::TaskEnvironment task_environment;
-  BrokerServices* broker = GetBroker();
+  CheckDesktopNameCommandTestRunner runner;
+  runner.SetTestState(BEFORE_INIT);
+  BrokerServices* broker = runner.broker();
 
+  ASSERT_TRUE(broker);
   // Precreate the desktop.
   EXPECT_EQ(SBOX_ALL_OK,
             broker->CreateAlternateDesktop(Desktop::kAlternateDesktop));
 
-  ASSERT_TRUE(broker);
-
-  base::CommandLine cmd_line = sandbox::CreateCommandLineForTesting("wait");
-
-  // Launch the app.
-  ResultCode result = SBOX_ALL_OK;
-  DWORD last_error = ERROR_SUCCESS;
-  base::win::ScopedProcessInformation target;
-
-  auto policy = broker->CreatePolicy();
-  policy->GetConfig()->SetDesktop(Desktop::kAlternateDesktop);
-  EXPECT_EQ(SBOX_ALL_OK, policy->GetConfig()->SetTokenLevel(USER_INTERACTIVE,
-                                                            USER_LOCKDOWN));
+  runner.GetConfig()->SetDesktop(Desktop::kAlternateDesktop);
   // Keep the desktop name to test against later (note - it was precreated).
   std::wstring desktop_name =
       broker->GetDesktopName(Desktop::kAlternateDesktop);
-  base::test::TestFuture<base::win::ScopedProcessInformation, DWORD, ResultCode>
-      test_future;
-  broker->SpawnTargetAsync(cmd_line, std::move(policy),
-                           test_future.GetCallback());
-  std::tie(target, last_error, result) = test_future.Take();
-
-  EXPECT_EQ(SBOX_ALL_OK, result);
-
-  EXPECT_EQ(1u, ::ResumeThread(target.thread_handle()));
-
-  EXPECT_EQ(static_cast<DWORD>(WAIT_TIMEOUT),
-            ::WaitForSingleObject(target.process_handle(), 2000));
-
-  EXPECT_NE(::GetThreadDesktop(target.thread_id()),
-            ::GetThreadDesktop(::GetCurrentThreadId()));
-
-  HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, false, DESKTOP_ENUMERATE);
-  EXPECT_TRUE(desk);
-  EXPECT_TRUE(::CloseDesktop(desk));
-  EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0));
-
-  ::WaitForSingleObject(target.process_handle(), INFINITE);
-
+  EXPECT_EQ(runner.RunTest(desktop_name), SBOX_TEST_SUCCEEDED);
   // Close the desktop handle.
   broker->DestroyDesktops();
-
   // Make sure the desktop does not exist anymore.
-  desk = ::OpenDesktop(desktop_name.c_str(), 0, false, DESKTOP_ENUMERATE);
+  HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, false, DESKTOP_ENUMERATE);
   EXPECT_FALSE(desk);
 }
 
-// Launches the app in the sandbox and ask it to wait in an
-// infinite loop. Waits for 2 seconds and then check if the
-// winstation associated with the app thread is not the same as the
-// current desktop.
+// Launches the app in the sandbox and check it was assigned the alternative
+// desktop and window station.
 TEST(PolicyTargetTest, WinstaPolicy) {
-  base::test::TaskEnvironment task_environment;
+  CheckDesktopNameCommandTestRunner runner;
+  runner.SetTestState(BEFORE_INIT);
   BrokerServices* broker = GetBroker();
+  ASSERT_TRUE(broker);
 
   // Precreate the desktop.
   EXPECT_EQ(SBOX_ALL_OK,
             broker->CreateAlternateDesktop(Desktop::kAlternateWinstation));
 
-  ASSERT_TRUE(broker);
-
-  base::CommandLine cmd_line = sandbox::CreateCommandLineForTesting("wait");
-
-  // Launch the app.
-  ResultCode result = SBOX_ALL_OK;
-  base::win::ScopedProcessInformation target;
-
-  auto policy = broker->CreatePolicy();
-  policy->GetConfig()->SetDesktop(Desktop::kAlternateWinstation);
-  EXPECT_EQ(SBOX_ALL_OK, policy->GetConfig()->SetTokenLevel(USER_INTERACTIVE,
-                                                            USER_LOCKDOWN));
-  DWORD last_error = ERROR_SUCCESS;
+  runner.GetConfig()->SetDesktop(Desktop::kAlternateWinstation);
   // Keep the desktop name for later (note - it was precreated).
   std::wstring desktop_name =
       broker->GetDesktopName(Desktop::kAlternateWinstation);
-  base::test::TestFuture<base::win::ScopedProcessInformation, DWORD, ResultCode>
-      test_future;
-  broker->SpawnTargetAsync(cmd_line, std::move(policy),
-                           test_future.GetCallback());
-  std::tie(target, last_error, result) = test_future.Take();
-
-  EXPECT_EQ(SBOX_ALL_OK, result);
-
-  EXPECT_EQ(1u, ::ResumeThread(target.thread_handle()));
-
-  EXPECT_EQ(static_cast<DWORD>(WAIT_TIMEOUT),
-            ::WaitForSingleObject(target.process_handle(), 2000));
-
-  EXPECT_NE(::GetThreadDesktop(target.thread_id()),
-            ::GetThreadDesktop(::GetCurrentThreadId()));
-
-  ASSERT_FALSE(desktop_name.empty());
 
   // Make sure there is a backslash, for the window station name.
-  EXPECT_NE(desktop_name.find_first_of(L'\\'), std::wstring::npos);
+  ASSERT_TRUE(desktop_name.contains(L'\\'));
+  EXPECT_EQ(runner.RunTest(desktop_name), SBOX_TEST_SUCCEEDED);
 
   // Isolate the desktop name.
   desktop_name = desktop_name.substr(desktop_name.find_first_of(L'\\') + 1);
@@ -421,10 +383,6 @@
   HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, false, DESKTOP_ENUMERATE);
   // This should fail if the desktop is really on another window station.
   EXPECT_FALSE(desk);
-  EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0));
-
-  ::WaitForSingleObject(target.process_handle(), INFINITE);
-
   // Close the desktop handle.
   broker->DestroyDesktops();
 }
@@ -466,60 +424,28 @@
 // Launches the app in the sandbox and share a handle with it. The app should
 // be able to use the handle.
 TEST(PolicyTargetTest, ShareHandleTest) {
-  base::test::TaskEnvironment task_environment;
-  BrokerServices* broker = GetBroker();
-  ASSERT_TRUE(broker);
-
-  std::string_view contents = "Hello World";
+  std::wstring_view contents = L"Hello World";
+  auto contents_span = base::as_byte_span(contents);
   base::WritableSharedMemoryRegion writable_region =
-      base::WritableSharedMemoryRegion::Create(contents.size());
+      base::WritableSharedMemoryRegion::Create(contents_span.size());
   ASSERT_TRUE(writable_region.IsValid());
   base::WritableSharedMemoryMapping writable_mapping = writable_region.Map();
   ASSERT_TRUE(writable_mapping.IsValid());
-  UNSAFE_TODO(
-      memcpy(writable_mapping.memory(), contents.data(), contents.size()));
-
-  // Get the path to the sandboxed app.
-  wchar_t prog_name[MAX_PATH];
-  GetModuleFileNameW(nullptr, prog_name, MAX_PATH);
+  std::ranges::copy(contents_span, base::span(writable_mapping).begin());
 
   base::ReadOnlySharedMemoryRegion read_only_region =
       base::WritableSharedMemoryRegion::ConvertToReadOnly(
           std::move(writable_region));
   ASSERT_TRUE(read_only_region.IsValid());
 
-  auto policy = broker->CreatePolicy();
-  policy->AddHandleToShare(read_only_region.GetPlatformHandle());
-
-  base::CommandLine cmd_line =
-      sandbox::CreateCommandLineForTesting("shared_memory_handle");
-  auto handle_str = base::NumberToString(
-      base::win::HandleToUint32(read_only_region.GetPlatformHandle()));
-  cmd_line.AppendArg(handle_str);
-
-  // Launch the app.
-  ResultCode result = SBOX_ALL_OK;
-  base::win::ScopedProcessInformation target;
-
-  EXPECT_EQ(SBOX_ALL_OK, policy->GetConfig()->SetTokenLevel(USER_INTERACTIVE,
-                                                            USER_LOCKDOWN));
-  DWORD last_error = ERROR_SUCCESS;
-  base::test::TestFuture<base::win::ScopedProcessInformation, DWORD, ResultCode>
-      test_future;
-  broker->SpawnTargetAsync(cmd_line, std::move(policy),
-                           test_future.GetCallback());
-  std::tie(target, last_error, result) = test_future.Take();
-
-  EXPECT_EQ(SBOX_ALL_OK, result);
-
-  EXPECT_EQ(1u, ::ResumeThread(target.thread_handle()));
-
-  EXPECT_EQ(static_cast<DWORD>(WAIT_TIMEOUT),
-            ::WaitForSingleObject(target.process_handle(), 2000));
-
-  EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0));
-
-  ::WaitForSingleObject(target.process_handle(), INFINITE);
+  SharedMemoryCommandTestRunner runner(JobLevel::kLockdown, USER_INTERACTIVE,
+                                       USER_LOCKDOWN);
+  runner.SetTestState(BEFORE_INIT);
+  runner.GetPolicy()->AddHandleToShare(read_only_region.GetPlatformHandle());
+  EXPECT_EQ(runner.RunTest(
+                base::win::HandleToUint32(read_only_region.GetPlatformHandle()),
+                contents),
+            SBOX_TEST_SUCCEEDED);
 }
 
 // Test if shared policies can be created by the broker.
@@ -568,23 +494,20 @@
 TEST(PolicyTargetTest, FilterEnvironment) {
   base::ScopedEnvironmentVariableOverride scoped_env("SBOX_TEST_ENV", "FOO");
   {
-    TestRunner runner;
-    runner.GetPolicy()->GetConfig()->SetFilterEnvironment(/*filter=*/true);
-    EXPECT_EQ(SBOX_TEST_SUCCEEDED,
-              runner.RunTest(L"PolicyTargetTest_filterEnvironment"));
+    FilterEnvironmentCommandTestRunner runner;
+    runner.GetConfig()->SetFilterEnvironment(/*filter=*/true);
+    EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest());
   }
   {
-    TestRunner runner;
-    runner.GetPolicy()->GetConfig()->SetFilterEnvironment(/*filter=*/false);
-    EXPECT_EQ(SBOX_TEST_SECOND_ERROR,
-              runner.RunTest(L"PolicyTargetTest_filterEnvironment"));
+    FilterEnvironmentCommandTestRunner runner;
+    runner.GetConfig()->SetFilterEnvironment(/*filter=*/false);
+    EXPECT_EQ(SBOX_TEST_SECOND_ERROR, runner.RunTest());
   }
 }
 
 TEST(PolicyTargetDeathTest, SharePseudoHandle) {
-  TestRunner runner;
-  auto* policy = runner.GetPolicy();
-  EXPECT_DEATH(policy->AddHandleToShare(::GetCurrentThread()), "");
+  SharedMemoryCommandTestRunner runner;
+  EXPECT_DEATH(runner.GetPolicy()->AddHandleToShare(::GetCurrentThread()), "");
 }
 
 }  // namespace sandbox
diff --git a/sandbox/win/tests/common/controller.cc b/sandbox/win/tests/common/controller.cc
index f40e337..8f33947 100644
--- a/sandbox/win/tests/common/controller.cc
+++ b/sandbox/win/tests/common/controller.cc
@@ -4,14 +4,16 @@
 
 #include "sandbox/win/tests/common/controller.h"
 
+#include <windows.h>
+
 #include <memory>
 #include <string>
 #include <string_view>
 
 #include "base/check.h"
+#include "base/check_op.h"
+#include "base/functional/bind.h"
 #include "base/functional/callback.h"
-#include "base/memory/platform_shared_memory_region.h"
-#include "base/memory/read_only_shared_memory_region.h"
 #include "base/no_destructor.h"
 #include "base/notreached.h"
 #include "base/path_service.h"
@@ -27,9 +29,6 @@
 #include "base/test/test_future.h"
 #include "base/test/test_timeouts.h"
 #include "base/time/time.h"
-#include "base/unguessable_token.h"
-#include "base/win/scoped_handle.h"
-#include "base/win/scoped_process_information.h"
 #include "base/win/windows_version.h"
 #include "sandbox/win/src/app_container.h"
 #include "sandbox/win/src/sandbox_factory.h"
@@ -84,73 +83,11 @@
   }
 };
 
-int SharedMemoryCommand(base::span<const std::wstring> args) {
-  if (args.empty()) {
-    return SBOX_TEST_INVALID_PARAMETER;
-  }
-  size_t raw_handle;
-  if (!base::StringToSizeT(args[0], &raw_handle)) {
-    return SBOX_TEST_INVALID_PARAMETER;
-  }
-  // First extract the handle to the platform-native ScopedHandle.
-  base::win::ScopedHandle scoped_handle(reinterpret_cast<HANDLE>(raw_handle));
-  if (!scoped_handle.is_valid()) {
-    return SBOX_TEST_INVALID_PARAMETER;
-  }
-
-  std::string_view test_contents = "Hello World";
-  // Then convert to the low-level chromium region.
-  base::subtle::PlatformSharedMemoryRegion platform_region =
-      base::subtle::PlatformSharedMemoryRegion::Take(
-          std::move(scoped_handle),
-          base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly,
-          test_contents.size(), base::UnguessableToken::Create());
-  // Finally wrap the low-level region in the shared memory API.
-  base::ReadOnlySharedMemoryRegion region =
-      base::ReadOnlySharedMemoryRegion::Deserialize(std::move(platform_region));
-  if (!region.IsValid()) {
-    return SBOX_TEST_INVALID_PARAMETER;
-  }
-  base::ReadOnlySharedMemoryMapping view = region.Map();
-  if (!view.IsValid()) {
-    return SBOX_TEST_INVALID_PARAMETER;
-  }
-  const std::string contents(view.GetMemoryAsSpan<char>().data());
-  if (contents != test_contents) {
-    return SBOX_TEST_INVALID_PARAMETER;
-  }
-  Sleep(INFINITE);
-  return SBOX_TEST_TIMED_OUT;
-}
-
 constexpr char kChildSwitch[] = "child";
 constexpr char kNoSandboxSwitch[] = "no-sandbox";
 constexpr char kStateSwitch[] = "state";
 constexpr char kCommandSwitch[] = "cmd";
-
-base::CommandLine CreateCommandLine(std::string_view command,
-                                    base::span<const std::string> args,
-                                    SboxTestsState state,
-                                    bool no_sandbox) {
-  // Get the path to the sandboxed process.
-  base::FilePath prog_name;
-  CHECK(base::PathService::Get(base::FILE_EXE, &prog_name));
-  base::CommandLine cmd_line(prog_name);
-  cmd_line.AppendSwitch(kChildSwitch);
-  if (no_sandbox) {
-    cmd_line.AppendSwitch(kNoSandboxSwitch);
-  }
-  DCHECK_LE(MAX_STATE, 10);
-  cmd_line.AppendSwitchASCII(kStateSwitch,
-                             base::NumberToString(static_cast<int>(state)));
-  cmd_line.AppendSwitchUTF8(kCommandSwitch, command);
-  cmd_line.AppendArg("--");
-  for (const auto& arg : args) {
-    cmd_line.AppendArg(arg);
-  }
-
-  return cmd_line;
-}
+constexpr char kLegacySwitch[] = "legacy";
 
 std::wstring MakePathToSysBase(std::wstring_view name,
                                std::wstring_view sysname,
@@ -175,8 +112,50 @@
   return MakePathToSysBase(name, L"SysWOW64", is_obj_man_path);
 }
 
+int RunLegacyCommand(CommandFunction func,
+                     base::span<const std::wstring> args) {
+  std::vector<const wchar_t*> argv;
+  for (const auto& arg : args) {
+    argv.push_back(&arg[0]);
+  }
+  return func(static_cast<int>(argv.size()), std::data(argv));
+}
+
+base::RepeatingCallback<int(base::span<const std::wstring>)> BindCommand(
+    const std::string& command_name,
+    bool legacy_command) {
+  HMODULE module;
+  if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+                               GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+                           reinterpret_cast<wchar_t*>(&DispatchCall),
+                           &module)) {
+    return {};
+  }
+
+  FARPROC func = ::GetProcAddress(module, command_name.c_str());
+  if (!func) {
+    return {};
+  }
+
+  if (legacy_command) {
+    return base::BindRepeating(RunLegacyCommand,
+                               reinterpret_cast<CommandFunction>(func));
+  } else {
+    return base::BindRepeating(reinterpret_cast<CommandFunctionArgs>(func));
+  }
+}
+
 }  // namespace
 
+namespace internal {
+
+template <>
+std::string ToString(const std::wstring& value) {
+  return base::WideToUTF8(value);
+}
+
+}  // namespace internal
+
 std::wstring MakePathToSys(std::wstring_view name, bool is_obj_man_path) {
   return (base::win::OSInfo::GetInstance()->IsWowX86OnAMD64())
              ? MakePathToSysWow64(name, is_obj_man_path)
@@ -220,56 +199,66 @@
   return instance;
 }
 
-TestRunner::TestRunner(JobLevel job_level,
-                       TokenLevel startup_token,
-                       TokenLevel main_token) {
-  broker_ = nullptr;
-  timeout_ = TestTimeouts::test_launcher_timeout();
+// static
+base::CommandLine TestRunnerBase::CreateCommandLine(
+    std::string_view command,
+    base::span<const std::string> args,
+    SboxTestsState state,
+    bool no_sandbox,
+    bool legacy_command) {
+  // Get the path to the sandboxed process.
+  base::FilePath prog_name;
+  CHECK(base::PathService::Get(base::FILE_EXE, &prog_name));
+  base::CommandLine cmd_line(prog_name);
+  cmd_line.AppendSwitch(kChildSwitch);
+  if (no_sandbox) {
+    cmd_line.AppendSwitch(kNoSandboxSwitch);
+  }
+  if (legacy_command) {
+    cmd_line.AppendSwitch(kLegacySwitch);
+  }
+  DCHECK_LE(MAX_STATE, 10);
+  cmd_line.AppendSwitchASCII(kStateSwitch,
+                             base::NumberToString(static_cast<int>(state)));
+  cmd_line.AppendSwitchUTF8(kCommandSwitch, command);
+  cmd_line.AppendArg("--");
+  for (const auto& arg : args) {
+    cmd_line.AppendArg(arg);
+  }
 
-  broker_ = GetBroker();
-  if (!broker_) {
-    return;
-  }
-  policy_ = broker_->CreatePolicy();
-  if (!policy_) {
-    return;
-  }
-  auto result = policy_->GetConfig()->SetJobLevel(job_level, 0);
-  if (result != SBOX_ALL_OK) {
-    return;
-  }
-  result = policy_->GetConfig()->SetTokenLevel(startup_token, main_token);
-  if (result != SBOX_ALL_OK) {
-    return;
-  }
-  is_init_ = true;
+  return cmd_line;
 }
 
-TestRunner::TestRunner()
-    : TestRunner(JobLevel::kLockdown,
-                 USER_RESTRICTED_SAME_ACCESS,
-                 USER_LOCKDOWN) {}
+TestRunnerBase::TestRunnerBase(JobLevel job_level,
+                               TokenLevel startup_token,
+                               TokenLevel main_token) {
+  timeout_ = TestTimeouts::test_launcher_timeout();
+  broker_ = GetBroker();
+  CHECK(broker_);
+  policy_ = broker_->CreatePolicy();
+  CHECK(policy_);
+  CHECK_EQ(SBOX_ALL_OK, policy_->GetConfig()->SetJobLevel(job_level, 0));
+  CHECK_EQ(SBOX_ALL_OK,
+           policy_->GetConfig()->SetTokenLevel(startup_token, main_token));
+}
 
-TargetPolicy* TestRunner::GetPolicy() {
+TestRunnerBase::~TestRunnerBase() = default;
+
+TargetPolicy* TestRunnerBase::GetPolicy() {
   return policy_.get();
 }
 
-TestRunner::~TestRunner() {
-  if (target_process_.IsValid() && kill_on_destruction_) {
-    target_process_.Terminate(0, /*wait=*/false);
-  }
+TargetConfig* TestRunnerBase::GetConfig() {
+  return GetPolicy()->GetConfig();
 }
 
-bool TestRunner::WaitForAllTargets() {
+bool TestRunnerBase::WaitForAllTargets() {
   TargetTracker::WaitForAllTargets();
   return true;
 }
 
-bool TestRunner::AllowFileAccess(FileSemantics semantics,
-                                 std::wstring_view pattern) {
-  if (!is_init_) {
-    return false;
-  }
+bool TestRunnerBase::AllowFileAccess(FileSemantics semantics,
+                                     std::wstring_view pattern) {
   if (policy_->GetConfig()->IsConfigured()) {
     return false;
   }
@@ -277,11 +266,8 @@
           policy_->GetConfig()->AllowFileAccess(semantics, pattern));
 }
 
-bool TestRunner::AddRuleSys32(FileSemantics semantics,
-                              std::wstring_view pattern) {
-  if (!is_init_) {
-    return false;
-  }
+bool TestRunnerBase::AddRuleSys32(FileSemantics semantics,
+                                  std::wstring_view pattern) {
   std::wstring win32_path = MakePathToSys32(pattern, false);
   if (win32_path.empty()) {
     return false;
@@ -300,40 +286,10 @@
   return AllowFileAccess(semantics, win32_path.c_str());
 }
 
-// TODO(forshaw): This is to support old code which passes and entire command
-// line. Remove once the new API is implemented and all the old tests have been
-// updated.
-int TestRunner::RunTest(std::wstring_view command) {
-  // Note: To use the `CommandLine` class we add a fake program and the switch
-  // terminator so that it doesn't sort switch arguments which can change the
-  // ordering.
-  std::wstring dummy_cmd_line = L"dummy -- ";
-  dummy_cmd_line += command;
-  auto cmd_line = base::CommandLine::FromString(dummy_cmd_line);
-  auto cmd_args = cmd_line.GetArgs();
-  if (cmd_args.empty()) {
-    return SBOX_TEST_FAILED_TO_RUN_TEST;
-  }
-  std::vector<std::string> args;
-  for (size_t i = 1; i < cmd_args.size(); ++i) {
-    args.emplace_back(base::WideToUTF8(cmd_args[i]));
-  }
-  return RunTestInternal(base::WideToUTF8(cmd_args[0]), args);
-}
-
-int TestRunner::RunTestInternal(std::string_view command,
-                                base::span<const std::string> args) {
-  if (!is_init_) {
-    return SBOX_TEST_FAILED_TO_RUN_TEST;
-  }
-  // For simplicity TestRunner supports only one process per instance.
-  if (target_process_.IsValid()) {
-    if (target_process_.IsRunning()) {
-      return SBOX_TEST_FAILED_TO_RUN_TEST;
-    }
-    target_process_.Close();
-  }
-
+base::Process TestRunnerBase::CreateTestProcess(
+    std::string_view command,
+    base::span<const std::string> args,
+    bool legacy_command) {
   if (disable_csrss_) {
     auto* config = policy_->GetConfig();
     if (config->GetAppContainer() == nullptr) {
@@ -342,26 +298,14 @@
   }
 
   // Launch the sandboxed process
-  auto cmd_line = CreateCommandLine(command, args, state_, no_sandbox_);
-  base::Process process = no_sandbox_ ? base::LaunchProcess(cmd_line, {})
-                                      : LaunchSandboxProcess(cmd_line);
+  auto cmd_line =
+      CreateCommandLine(command, args, state_, no_sandbox_, legacy_command);
+  return no_sandbox_ ? base::LaunchProcess(cmd_line, {})
+                     : LaunchSandboxProcess(cmd_line);
+}
 
-  if (!process.IsValid()) {
-    return SBOX_TEST_FAILED_TO_RUN_TEST;
-  }
-
-  // For an asynchronous run we don't bother waiting.
-  if (is_async_) {
-    target_process_ = std::move(process);
-    return SBOX_TEST_SUCCEEDED;
-  }
-
-  base::TimeDelta timeout = timeout_;
-  if (::IsDebuggerPresent()) {
-    // Don't kill the target process on a time-out while we are debugging.
-    timeout = base::TimeDelta::Max();
-  }
-
+int TestRunnerBase::WaitForResult(const base::Process& process) const {
+  auto timeout = ::IsDebuggerPresent() ? base::TimeDelta::Max() : timeout_;
   int exit_code = SBOX_TEST_SUCCEEDED;
   if (!process.WaitForExitWithTimeout(timeout, &exit_code)) {
     return SBOX_TEST_TIMED_OUT;
@@ -370,7 +314,7 @@
   return exit_code;
 }
 
-base::Process TestRunner::LaunchSandboxProcess(
+base::Process TestRunnerBase::LaunchSandboxProcess(
     const base::CommandLine& cmd_line) {
   ResultCode result = SBOX_ALL_OK;
   DWORD last_error = ERROR_SUCCESS;
@@ -399,12 +343,12 @@
   return base::Process(proc_info.TakeProcessHandle());
 }
 
-void TestRunner::SetTimeout(DWORD timeout_ms) {
+void TestRunnerBase::SetTimeout(DWORD timeout_ms) {
   SetTimeout(timeout_ms == INFINITE ? base::TimeDelta::Max()
                                     : base::Milliseconds(timeout_ms));
 }
 
-void TestRunner::SetTimeout(base::TimeDelta timeout) {
+void TestRunnerBase::SetTimeout(base::TimeDelta timeout) {
   // We do not take -ve timeouts.
   DCHECK(timeout >= base::TimeDelta());
   // We need millisecond DWORDS but also cannot take exactly INFINITE,
@@ -413,11 +357,48 @@
   timeout_ = timeout;
 }
 
-DWORD TestRunner::timeout_ms() {
-  if (timeout_.is_inf()) {
-    return INFINITE;
+TestRunner::~TestRunner() {
+  if (target_process_.IsValid() && kill_on_destruction_) {
+    target_process_.Terminate(0, /*wait=*/false);
   }
-  return static_cast<DWORD>(timeout_.InMilliseconds());
+}
+
+int TestRunner::RunTest(std::wstring_view command) {
+  // For simplicity TestRunner supports only one process per instance.
+  if (target_process_.IsValid()) {
+    if (target_process_.IsRunning()) {
+      return SBOX_TEST_FAILED_TO_RUN_TEST;
+    }
+    target_process_.Close();
+  }
+
+  // Note: To use the `CommandLine` class we add a fake program and the switch
+  // terminator so that it doesn't sort switch arguments which can change the
+  // ordering.
+  std::wstring dummy_cmd_line = L"dummy -- ";
+  dummy_cmd_line += command;
+  auto cmd_line = base::CommandLine::FromString(dummy_cmd_line);
+  auto cmd_args = cmd_line.GetArgs();
+  if (cmd_args.empty()) {
+    return SBOX_TEST_FAILED_TO_RUN_TEST;
+  }
+  std::vector<std::string> args;
+  for (size_t i = 1; i < cmd_args.size(); ++i) {
+    args.emplace_back(base::WideToUTF8(cmd_args[i]));
+  }
+  auto process = CreateTestProcess(base::WideToUTF8(cmd_args[0]), args,
+                                   /*legacy_command=*/true);
+  if (!process.IsValid()) {
+    return SBOX_TEST_FAILED_TO_RUN_TEST;
+  }
+
+  // For an asynchronous run we don't bother waiting.
+  if (is_async_) {
+    target_process_ = std::move(process);
+    return SBOX_TEST_SUCCEEDED;
+  }
+
+  return WaitForResult(process);
 }
 
 bool IsChildProcessForTesting() {
@@ -447,21 +428,15 @@
   }
   auto args = cmd_line->GetArgs();
   // We hard code two tests to avoid dispatch failures.
-  if (command_name == "wait") {
+  if (command_name == sandbox::WaitCommandTestRunner::type::kTestName) {
     Sleep(INFINITE);
     return SBOX_TEST_TIMED_OUT;
   }
 
-  if (command_name == "ping") {
+  if (command_name == PingCommandTestRunner::type::kTestName) {
     return SBOX_TEST_PING_OK;
   }
 
-  // If the caller shared a shared memory handle with us attempt to open it
-  // in read only mode and sleep infinitely if we succeed.
-  if (command_name == "shared_memory_handle") {
-    return SharedMemoryCommand(args);
-  }
-
   int state_value;
   if (!base::StringToInt(cmd_line->GetSwitchValueASCII(kStateSwitch),
                          &state_value)) {
@@ -480,20 +455,14 @@
     return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
   }
 
-  CommandFunction command = reinterpret_cast<CommandFunction>(
-                                ::GetProcAddress(module, command_name.c_str()));
+  auto command = BindCommand(command_name, cmd_line->HasSwitch(kLegacySwitch));
   if (!command) {
     return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
   }
-  std::vector<wchar_t*> argv;
-  for (auto& arg : args) {
-    argv.push_back(&arg[0]);
-  }
-  int argc = static_cast<int>(argv.size());
   if (BEFORE_INIT == state) {
-    return command(argc, std::data(argv));
+    return command.Run(args);
   } else if (EVERY_STATE == state) {
-    command(argc, std::data(argv));
+    command.Run(args);
   }
   TargetServices* target = SandboxFactory::GetTargetServices();
   if (target) {
@@ -501,9 +470,9 @@
       return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
     }
     if (BEFORE_REVERT == state) {
-      return command(argc, std::data(argv));
+      return command.Run(args);
     } else if (EVERY_STATE == state) {
-      command(argc, std::data(argv));
+      command.Run(args);
     }
 #if defined(ADDRESS_SANITIZER) || CHECK_WILL_STREAM()
     // Bind and leak dbghelp.dll before the token is lowered, otherwise some
@@ -519,11 +488,7 @@
     return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
   }
 
-  return command(argc, std::data(argv));
-}
-
-base::CommandLine CreateCommandLineForTesting(std::string_view command) {
-  return CreateCommandLine(command, {}, MIN_STATE, /*no_sandbox=*/false);
+  return command.Run(args);
 }
 
 }  // namespace sandbox
diff --git a/sandbox/win/tests/common/controller.h b/sandbox/win/tests/common/controller.h
index 380e627d..e0a12011 100644
--- a/sandbox/win/tests/common/controller.h
+++ b/sandbox/win/tests/common/controller.h
@@ -5,21 +5,45 @@
 #ifndef SANDBOX_WIN_TESTS_COMMON_CONTROLLER_H_
 #define SANDBOX_WIN_TESTS_COMMON_CONTROLLER_H_
 
+#include <concepts>
 #include <string>
 #include <string_view>
+#include <utility>
 
 #include "base/command_line.h"
+#include "base/compiler_specific.h"
 #include "base/containers/span.h"
 #include "base/dcheck_is_on.h"
 #include "base/memory/raw_ptr.h"
 #include "base/process/process.h"
+#include "base/strings/to_string.h"
 #include "base/time/time.h"
+#include "base/win/scoped_process_information.h"
 #include "base/win/windows_types.h"
 #include "sandbox/win/src/sandbox_policy.h"
 #include "sandbox/win/src/win_utils.h"
 
 namespace sandbox {
 
+namespace internal {
+
+// base::ToString can't handle passing std::wstring so implement our own wrapper
+// to handle this special case.
+template <typename T>
+std::string ToString(const T& value) {
+  return base::ToString(value);
+}
+
+template <>
+std::string ToString(const std::wstring& value);
+
+template <typename T>
+concept TestNameDefinition = requires {
+  { T::kTestName } -> std::convertible_to<std::string_view>;
+};
+
+}  // namespace internal
+
 // See winerror.h for details.
 #define SEVERITY_INFO_FLAGS   0x40000000
 #define SEVERITY_ERROR_FLAGS  0xC0000000
@@ -90,19 +114,32 @@
 #define SBOX_TESTS_API __declspec(dllexport)
 #define SBOX_TESTS_COMMAND extern "C" SBOX_TESTS_API
 
+#define SBOX_TEST_DECLARE_COMMAND(name)                         \
+  struct name##Def {                                            \
+    static constexpr std::string_view kTestName = #name "Impl"; \
+  };                                                            \
+  using name##TestRunner = GenericTestRunner<name##Def>
+
+#define SBOX_TEST_DEFINE_COMMAND(name) \
+  SBOX_TESTS_COMMAND int name##Impl(base::span<const std::wstring> args)
+
+// Declare a command runner type and its implementation.
+#define SBOX_TEST_COMMAND(name)    \
+  SBOX_TEST_DECLARE_COMMAND(name); \
+  SBOX_TEST_DEFINE_COMMAND(name)
+
 extern "C" {
-typedef int (*CommandFunction)(int argc, wchar_t **argv);
+typedef int (*CommandFunction)(int argc, const wchar_t** argv);
 }
 
+typedef int (*CommandFunctionArgs)(base::span<const std::wstring>);
+
 // Class to facilitate the launch of a test inside the sandbox.
-class TestRunner {
+class TestRunnerBase {
  public:
-  TestRunner(JobLevel job_level, TokenLevel startup_token,
-             TokenLevel main_token);
-
-  TestRunner();
-
-  ~TestRunner();
+  TestRunnerBase(const TestRunnerBase&) = delete;
+  TestRunnerBase& operator=(const TestRunnerBase&) = delete;
+  virtual ~TestRunnerBase();
 
   // Adds a filesystem rules with the path of a file in system32. The function
   // appends "pattern" to "system32" and then call AddRule. Return true if the
@@ -113,17 +150,10 @@
   // succeeds.
   bool AllowFileAccess(FileSemantics semantics, std::wstring_view pattern);
 
-  // Starts a child process in the sandbox and ask it to run `command`.
-  // Return a SboxTestResult.
-  int RunTest(std::wstring_view command);
-
   // Sets the timeout value for the child to run the command and return.
   void SetTimeout(DWORD timeout_ms);
   void SetTimeout(base::TimeDelta timeout);
 
-  // Sets TestRunner to return without waiting for the process to exit.
-  void SetAsynchronous(bool is_async) { is_async_ = is_async; }
-
   // Sets whether TestRunner sandboxes the child process. ("--no-sandbox")
   void SetUnsandboxed(bool is_no_sandbox) { no_sandbox_ = is_no_sandbox; }
 
@@ -134,43 +164,131 @@
   // Sets the desired state for the test to run.
   void SetTestState(SboxTestsState desired_state) { state_ = desired_state; }
 
-  // Sets a flag whether the process should be killed when the TestRunner is
-  // destroyed.
-  void SetKillOnDestruction(bool value) { kill_on_destruction_ = value; }
-
-  // Returns the pointers to the policy object. It can be used to modify
+  // Returns the pointer to the policy object. It can be used to modify
   // the policy manually.
   TargetPolicy* GetPolicy();
 
+  // Returns the pointer to the config object. It can be used to modify
+  // the config manually.
+  TargetConfig* GetConfig();
+
   BrokerServices* broker() { return broker_; }
 
+  // Blocks until the number of tracked processes returns to zero.
+  bool WaitForAllTargets();
+
+ protected:
+  static base::CommandLine CreateCommandLine(std::string_view command,
+                                             base::span<const std::string> args,
+                                             SboxTestsState state,
+                                             bool no_sandbox,
+                                             bool legacy_command);
+
+  TestRunnerBase(JobLevel job_level,
+                 TokenLevel startup_token,
+                 TokenLevel main_token);
+
+  base::Process CreateTestProcess(std::string_view command,
+                                  base::span<const std::string> args,
+                                  bool legacy_command = false);
+
+  int WaitForResult(const base::Process& process) const;
+
+ private:
+  base::Process LaunchSandboxProcess(const base::CommandLine& cmd_line);
+
+  raw_ptr<BrokerServices> broker_;
+  std::unique_ptr<TargetPolicy> policy_;
+  base::TimeDelta timeout_;
+  SboxTestsState state_ = AFTER_REVERT;
+  bool no_sandbox_ = false;
+  bool disable_csrss_ = true;
+};
+
+template <internal::TestNameDefinition Test>
+class GenericTestRunner final : public TestRunnerBase {
+ public:
+  using type = Test;
+
+  GenericTestRunner()
+      : TestRunnerBase(JobLevel::kLockdown,
+                       USER_RESTRICTED_SAME_ACCESS,
+                       USER_LOCKDOWN) {}
+
+  GenericTestRunner(JobLevel job_level,
+                    TokenLevel startup_token,
+                    TokenLevel main_token)
+      : TestRunnerBase(job_level, startup_token, main_token) {}
+
+  static base::CommandLine CreateCommandLineForTesting() {
+    return CreateCommandLine(Test::kTestName, {}, BEFORE_INIT,
+                             /*no_sandbox=*/false,
+                             /*legacy_command=*/false);
+  }
+
+  // Starts a child process in the sandbox and ask it to run the callback
+  // command with optional arguments asynchronously. Return a running process
+  // object.
+  template <typename... Args>
+  base::Process RunTestAsync(Args&&... args) {
+    std::vector<std::string> args_vector = {internal::ToString(args)...};
+    return CreateTestProcess(Test::kTestName, args_vector);
+  }
+
+  // Starts a child process in the sandbox and ask it to run the callback
+  // command with optional arguments. Return a SboxTestResult.
+  template <typename... Args>
+  int RunTest(Args&&... args) {
+    base::Process process = RunTestAsync(std::forward<Args>(args)...);
+    if (!process.IsValid()) {
+      return SBOX_TEST_FAILED_TO_RUN_TEST;
+    }
+    return WaitForResult(process);
+  }
+};
+
+// TODO(forshaw): This is to support old code which passes and entire command
+// line. Remove once the new API is implemented and all the old tests have been
+// updated.
+class TestRunner final : public TestRunnerBase {
+ public:
+  TestRunner()
+      : TestRunnerBase(JobLevel::kLockdown,
+                       USER_RESTRICTED_SAME_ACCESS,
+                       USER_LOCKDOWN) {}
+  TestRunner(JobLevel job_level,
+             TokenLevel startup_token,
+             TokenLevel main_token)
+      : TestRunnerBase(job_level, startup_token, main_token) {}
+  ~TestRunner() override;
+
+  // Sets TestRunner to return without waiting for the process to exit.
+  void SetAsynchronous(bool is_async) { is_async_ = is_async; }
+
+  // Sets a flag whether the process should be killed when the TestRunner is
+  // destroyed.
+  void SetKillOnDestruction(bool value) { kill_on_destruction_ = value; }
+
   // Returns the process handle for an asynchronous test.
   base::ProcessHandle process() { return target_process_.Handle(); }
 
   // Returns the process ID for an asynchronous test.
   base::ProcessId process_id() { return target_process_.Pid(); }
 
-  // Blocks until the number of tracked processes returns to zero.
-  bool WaitForAllTargets();
+  // Starts a child process in the sandbox and ask it to run `command`.
+  // Return a SboxTestResult.
+  int RunTest(std::wstring_view command);
 
  private:
-  DWORD timeout_ms();
-  int RunTestInternal(std::string_view command,
-                      base::span<const std::string> args);
-  base::Process LaunchSandboxProcess(const base::CommandLine& cmd_line);
-
-  raw_ptr<BrokerServices> broker_;
-  std::unique_ptr<TargetPolicy> policy_;
-  base::TimeDelta timeout_;
-  SboxTestsState state_ = AFTER_REVERT;
-  bool is_init_ = false;
   bool is_async_ = false;
-  bool no_sandbox_ = false;
-  bool disable_csrss_ = true;
   bool kill_on_destruction_ = true;
   base::Process target_process_;
 };
 
+// Declare built-in test commands.
+SBOX_TEST_DECLARE_COMMAND(WaitCommand);
+SBOX_TEST_DECLARE_COMMAND(PingCommand);
+
 // Returns the broker services.
 BrokerServices* GetBroker();
 
@@ -184,9 +302,6 @@
 // Runs the given test on the target process.
 int DispatchCall();
 
-// Create a command line object for directly calling `SpawnTargetAsync`.
-base::CommandLine CreateCommandLineForTesting(std::string_view command);
-
 }  // namespace sandbox
 
 #endif  // SANDBOX_WIN_TESTS_COMMON_CONTROLLER_H_
diff --git a/sandbox/win/tests/validation_tests/suite.cc b/sandbox/win/tests/validation_tests/suite.cc
index b6ec078..32d2e8f 100644
--- a/sandbox/win/tests/validation_tests/suite.cc
+++ b/sandbox/win/tests/validation_tests/suite.cc
@@ -88,8 +88,8 @@
 
 // Tests if the suite is working properly.
 TEST(ValidationSuite, TestSuite) {
-  TestRunner runner;
-  ASSERT_EQ(SBOX_TEST_PING_OK, runner.RunTest(L"ping"));
+  PingCommandTestRunner runner;
+  ASSERT_EQ(SBOX_TEST_PING_OK, runner.RunTest());
 }
 
 // Tests if the file system is correctly protected by the sandbox.
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index b28dd3ec..8d0c90f 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -6172,28 +6172,6 @@
             ]
         }
     ],
-    "ClientSideDetectionNewObservers": [
-        {
-            "platforms": [
-                "android",
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "params": {
-                        "ClassificationDelay": "0.0"
-                    },
-                    "enable_features": [
-                        "ClientSideDetectionNewObservers"
-                    ]
-                }
-            ]
-        }
-    ],
     "ClientSideDetectionOnDeviceModelLazyDownloadAndroid": [
         {
             "platforms": [
@@ -19306,6 +19284,21 @@
             ]
         }
     ],
+    "PdfSaveToDrive_ChromeOS": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "PdfSaveToDrive"
+                    ]
+                }
+            ]
+        }
+    ],
     "PdfUseShowSaveFilePicker": [
         {
             "platforms": [
diff --git a/third_party/android_deps/autorolled/VERSION.txt b/third_party/android_deps/autorolled/VERSION.txt
index b64ce9e..1dbbe40 100644
--- a/third_party/android_deps/autorolled/VERSION.txt
+++ b/third_party/android_deps/autorolled/VERSION.txt
@@ -1 +1 @@
-b5b60068c40971c.bc7f3b9ff45a220
\ No newline at end of file
+5f2ae8ba61d6331.0dc852e54189258
\ No newline at end of file
diff --git a/third_party/android_deps/autorolled/bill_of_materials.json b/third_party/android_deps/autorolled/bill_of_materials.json
index 72ee347..94c0f20 100644
--- a/third_party/android_deps/autorolled/bill_of_materials.json
+++ b/third_party/android_deps/autorolled/bill_of_materials.json
@@ -1437,7 +1437,7 @@
     {
         "name": "error_prone_annotations",
         "group": "com.google.errorprone",
-        "version": "2.47.0"
+        "version": "2.48.0"
     },
     {
         "name": "firebase-annotations",
@@ -1537,7 +1537,7 @@
     {
         "name": "protobuf-javalite",
         "group": "com.google.protobuf",
-        "version": "4.34.0-RC2"
+        "version": "4.34.0"
     },
     {
         "name": "protobuf-lite",
diff --git a/third_party/android_deps/autorolled/build.gradle b/third_party/android_deps/autorolled/build.gradle
index a85f219..73347de 100644
--- a/third_party/android_deps/autorolled/build.gradle
+++ b/third_party/android_deps/autorolled/build.gradle
@@ -301,7 +301,7 @@
 versionCache['com.google.code.findbugs:jsr305'] = '3.0.2'
 versionCache['com.google.code.gson:gson'] = '2.13.2'
 versionCache['com.google.errorprone:error_prone_annotation'] = '2.41.0'
-versionCache['com.google.errorprone:error_prone_annotations'] = '2.47.0'
+versionCache['com.google.errorprone:error_prone_annotations'] = '2.48.0'
 versionCache['com.google.firebase:firebase-annotations'] = '17.0.0'
 versionCache['com.google.firebase:firebase-common'] = '22.0.1'
 versionCache['com.google.firebase:firebase-components'] = '19.0.0'
@@ -321,7 +321,7 @@
 versionCache['com.google.mlkit:common'] = '18.11.0'
 versionCache['com.google.mlkit:genai-common'] = '1.0.0-beta3'
 versionCache['com.google.mlkit:genai-prompt'] = '1.0.0-beta1'
-versionCache['com.google.protobuf:protobuf-javalite'] = '4.34.0-RC2'
+versionCache['com.google.protobuf:protobuf-javalite'] = '4.34.0'
 versionCache['com.google.protobuf:protobuf-lite'] = '3.0.1'
 versionCache['com.google.testparameterinjector:test-parameter-injector'] = '1.18'
 versionCache['com.googlecode.java-diff-utils:diffutils'] = '1.3.0'
diff --git a/third_party/android_deps/autorolled/committed/libs/com_google_errorprone_error_prone_annotations/README.chromium b/third_party/android_deps/autorolled/committed/libs/com_google_errorprone_error_prone_annotations/README.chromium
index c037234..a229616a 100644
--- a/third_party/android_deps/autorolled/committed/libs/com_google_errorprone_error_prone_annotations/README.chromium
+++ b/third_party/android_deps/autorolled/committed/libs/com_google_errorprone_error_prone_annotations/README.chromium
@@ -1,7 +1,7 @@
 Name: error-prone annotations
 Short Name: error_prone_annotations
-URL: https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.47.0/error_prone_annotations-2.47.0.jar
-Version: 2.47.0
+URL: https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.48.0/error_prone_annotations-2.48.0.jar
+Version: 2.48.0
 Update Mechanism: Autoroll
 License: Apache-2.0
 License File: LICENSE
diff --git a/third_party/android_deps/autorolled/committed/libs/com_google_protobuf_protobuf_javalite/README.chromium b/third_party/android_deps/autorolled/committed/libs/com_google_protobuf_protobuf_javalite/README.chromium
index 27af6088..bd1b5f74 100644
--- a/third_party/android_deps/autorolled/committed/libs/com_google_protobuf_protobuf_javalite/README.chromium
+++ b/third_party/android_deps/autorolled/committed/libs/com_google_protobuf_protobuf_javalite/README.chromium
@@ -1,7 +1,7 @@
 Name: Protocol Buffers [Lite]
 Short Name: protobuf-javalite
-URL: https://repo.maven.apache.org/maven2/com/google/protobuf/protobuf-javalite/4.34.0-RC2/protobuf-javalite-4.34.0-RC2.jar
-Version: 4.34.0-RC2
+URL: https://repo.maven.apache.org/maven2/com/google/protobuf/protobuf-javalite/4.34.0/protobuf-javalite-4.34.0.jar
+Version: 4.34.0
 Update Mechanism: Autoroll
 License: BSD-3-Clause
 License File: LICENSE
diff --git a/third_party/androidx/build.gradle b/third_party/androidx/build.gradle
index d4382c7..aafed27 100644
--- a/third_party/androidx/build.gradle
+++ b/third_party/androidx/build.gradle
@@ -323,7 +323,7 @@
     google()
     maven {
         // This URL is generated by the fetch_all_androidx.py script.
-        url 'https://androidx.dev/snapshots/builds/14950004/artifacts/repository'
+        url 'https://androidx.dev/snapshots/builds/14954281/artifacts/repository'
     }
     mavenCentral()
 }
diff --git a/third_party/androidx/committed/libs/androidx_activity_activity/README.chromium b/third_party/androidx/committed/libs/androidx_activity_activity/README.chromium
index 203d739..493111ab 100644
--- a/third_party/androidx/committed/libs/androidx_activity_activity/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_activity_activity/README.chromium
@@ -1,6 +1,6 @@
 Name: Activity
 Short Name: activity
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/activity/activity/1.13.0-SNAPSHOT/activity-1.13.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/activity/activity/1.13.0-SNAPSHOT/activity-1.13.0-20260228.045313-1.aar
 Version: 1.13.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_activity_activity_compose/README.chromium b/third_party/androidx/committed/libs/androidx_activity_activity_compose/README.chromium
index 9dc3460..392539fa 100644
--- a/third_party/androidx/committed/libs/androidx_activity_activity_compose/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_activity_activity_compose/README.chromium
@@ -1,6 +1,6 @@
 Name: Activity Compose
 Short Name: activity-compose
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/activity/activity-compose/1.13.0-SNAPSHOT/activity-compose-1.13.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/activity/activity-compose/1.13.0-SNAPSHOT/activity-compose-1.13.0-20260228.045313-1.aar
 Version: 1.13.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_activity_activity_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_activity_activity_ktx/README.chromium
index f7e5010d..748e91a6 100644
--- a/third_party/androidx/committed/libs/androidx_activity_activity_ktx/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_activity_activity_ktx/README.chromium
@@ -1,6 +1,6 @@
 Name: Activity Kotlin Extensions
 Short Name: activity-ktx
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/activity/activity-ktx/1.13.0-SNAPSHOT/activity-ktx-1.13.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/activity/activity-ktx/1.13.0-SNAPSHOT/activity-ktx-1.13.0-20260228.045313-1.aar
 Version: 1.13.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_annotation_annotation_experimental/README.chromium b/third_party/androidx/committed/libs/androidx_annotation_annotation_experimental/README.chromium
index 0ee88cfb..ccd961dd 100644
--- a/third_party/androidx/committed/libs/androidx_annotation_annotation_experimental/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_annotation_annotation_experimental/README.chromium
@@ -1,6 +1,6 @@
 Name: Experimental annotation
 Short Name: annotation-experimental
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/annotation/annotation-experimental/1.6.0-SNAPSHOT/annotation-experimental-1.6.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/annotation/annotation-experimental/1.6.0-SNAPSHOT/annotation-experimental-1.6.0-20260228.045313-1.aar
 Version: 1.6.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_annotation_annotation_jvm/README.chromium b/third_party/androidx/committed/libs/androidx_annotation_annotation_jvm/README.chromium
index 2cfdd2fa..dbcd044e 100644
--- a/third_party/androidx/committed/libs/androidx_annotation_annotation_jvm/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_annotation_annotation_jvm/README.chromium
@@ -1,6 +1,6 @@
 Name: Annotation
 Short Name: annotation-jvm
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/annotation/annotation-jvm/1.10.0-SNAPSHOT/annotation-jvm-1.10.0-20260227.122158-1.jar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/annotation/annotation-jvm/1.10.0-SNAPSHOT/annotation-jvm-1.10.0-20260228.045313-1.jar
 Version: 1.10.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_appcompat_appcompat/README.chromium b/third_party/androidx/committed/libs/androidx_appcompat_appcompat/README.chromium
index a90be23..4ca0833e 100644
--- a/third_party/androidx/committed/libs/androidx_appcompat_appcompat/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_appcompat_appcompat/README.chromium
@@ -1,6 +1,6 @@
 Name: AppCompat
 Short Name: appcompat
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/appcompat/appcompat/1.8.0-SNAPSHOT/appcompat-1.8.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/appcompat/appcompat/1.8.0-SNAPSHOT/appcompat-1.8.0-20260228.045313-1.aar
 Version: 1.8.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_appcompat_appcompat_resources/README.chromium b/third_party/androidx/committed/libs/androidx_appcompat_appcompat_resources/README.chromium
index 84b3cbb..f4e4057d 100644
--- a/third_party/androidx/committed/libs/androidx_appcompat_appcompat_resources/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_appcompat_appcompat_resources/README.chromium
@@ -1,6 +1,6 @@
 Name: AppCompat Resources
 Short Name: appcompat-resources
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/appcompat/appcompat-resources/1.8.0-SNAPSHOT/appcompat-resources-1.8.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/appcompat/appcompat-resources/1.8.0-SNAPSHOT/appcompat-resources-1.8.0-20260228.045313-1.aar
 Version: 1.8.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_appsearch_appsearch/README.chromium b/third_party/androidx/committed/libs/androidx_appsearch_appsearch/README.chromium
index 7591f1e0..4975584 100644
--- a/third_party/androidx/committed/libs/androidx_appsearch_appsearch/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_appsearch_appsearch/README.chromium
@@ -1,6 +1,6 @@
 Name: AppSearch
 Short Name: appsearch
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/appsearch/appsearch/1.2.0-SNAPSHOT/appsearch-1.2.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/appsearch/appsearch/1.2.0-SNAPSHOT/appsearch-1.2.0-20260228.045313-1.aar
 Version: 1.2.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_appsearch_appsearch_builtin_types/README.chromium b/third_party/androidx/committed/libs/androidx_appsearch_appsearch_builtin_types/README.chromium
index 76a8a4f..78e020c0 100644
--- a/third_party/androidx/committed/libs/androidx_appsearch_appsearch_builtin_types/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_appsearch_appsearch_builtin_types/README.chromium
@@ -1,6 +1,6 @@
 Name: AppSearch Builtin Types
 Short Name: appsearch-builtin-types
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/appsearch/appsearch-builtin-types/1.2.0-SNAPSHOT/appsearch-builtin-types-1.2.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/appsearch/appsearch-builtin-types/1.2.0-SNAPSHOT/appsearch-builtin-types-1.2.0-20260228.045313-1.aar
 Version: 1.2.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_appsearch_appsearch_platform_storage/README.chromium b/third_party/androidx/committed/libs/androidx_appsearch_appsearch_platform_storage/README.chromium
index 93390d1..2448667 100644
--- a/third_party/androidx/committed/libs/androidx_appsearch_appsearch_platform_storage/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_appsearch_appsearch_platform_storage/README.chromium
@@ -1,6 +1,6 @@
 Name: AppSearch Platform Storage
 Short Name: appsearch-platform-storage
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/appsearch/appsearch-platform-storage/1.2.0-SNAPSHOT/appsearch-platform-storage-1.2.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/appsearch/appsearch-platform-storage/1.2.0-SNAPSHOT/appsearch-platform-storage-1.2.0-20260228.045313-1.aar
 Version: 1.2.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_arch_core_core_common/README.chromium b/third_party/androidx/committed/libs/androidx_arch_core_core_common/README.chromium
index e6ceb77..1bc1310 100644
--- a/third_party/androidx/committed/libs/androidx_arch_core_core_common/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_arch_core_core_common/README.chromium
@@ -1,6 +1,6 @@
 Name: Arch-Common
 Short Name: core-common
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/arch/core/core-common/2.3.0-SNAPSHOT/core-common-2.3.0-20260227.122158-1.jar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/arch/core/core-common/2.3.0-SNAPSHOT/core-common-2.3.0-20260228.045313-1.jar
 Version: 2.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_arch_core_core_runtime/README.chromium b/third_party/androidx/committed/libs/androidx_arch_core_core_runtime/README.chromium
index db8ac64..d605a8a 100644
--- a/third_party/androidx/committed/libs/androidx_arch_core_core_runtime/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_arch_core_core_runtime/README.chromium
@@ -1,6 +1,6 @@
 Name: Arch-Runtime
 Short Name: core-runtime
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/arch/core/core-runtime/2.3.0-SNAPSHOT/core-runtime-2.3.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/arch/core/core-runtime/2.3.0-SNAPSHOT/core-runtime-2.3.0-20260228.045313-1.aar
 Version: 2.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_autofill_autofill/README.chromium b/third_party/androidx/committed/libs/androidx_autofill_autofill/README.chromium
index 7d30ef3b..d5b37d34 100644
--- a/third_party/androidx/committed/libs/androidx_autofill_autofill/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_autofill_autofill/README.chromium
@@ -1,6 +1,6 @@
 Name: Autofill
 Short Name: autofill
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/autofill/autofill/1.4.0-SNAPSHOT/autofill-1.4.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/autofill/autofill/1.4.0-SNAPSHOT/autofill-1.4.0-20260228.045313-1.aar
 Version: 1.4.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_common/README.chromium b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_common/README.chromium
index 38ba6e5..7670098 100644
--- a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_common/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_common/README.chromium
@@ -1,6 +1,6 @@
 Name: Benchmark - Common
 Short Name: benchmark-common
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/benchmark/benchmark-common/1.5.0-SNAPSHOT/benchmark-common-1.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/benchmark/benchmark-common/1.5.0-SNAPSHOT/benchmark-common-1.5.0-20260228.045313-1.aar
 Version: 1.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_junit4/README.chromium b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_junit4/README.chromium
index 377274a..f448b38 100644
--- a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_junit4/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_junit4/README.chromium
@@ -1,6 +1,6 @@
 Name: Benchmark - JUnit4
 Short Name: benchmark-junit4
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/benchmark/benchmark-junit4/1.5.0-SNAPSHOT/benchmark-junit4-1.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/benchmark/benchmark-junit4/1.5.0-SNAPSHOT/benchmark-junit4-1.5.0-20260228.045313-1.aar
 Version: 1.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro/README.chromium b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro/README.chromium
index d9fc6b6..ede9833 100644
--- a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro/README.chromium
@@ -1,6 +1,6 @@
 Name: Benchmark - Macrobenchmark
 Short Name: benchmark-macro
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/benchmark/benchmark-macro/1.5.0-SNAPSHOT/benchmark-macro-1.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/benchmark/benchmark-macro/1.5.0-SNAPSHOT/benchmark-macro-1.5.0-20260228.045313-1.aar
 Version: 1.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro_junit4/README.chromium b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro_junit4/README.chromium
index 996df69..bb0411e 100644
--- a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro_junit4/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro_junit4/README.chromium
@@ -1,6 +1,6 @@
 Name: Benchmark - Macrobenchmark JUnit4
 Short Name: benchmark-macro-junit4
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/benchmark/benchmark-macro-junit4/1.5.0-SNAPSHOT/benchmark-macro-junit4-1.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/benchmark/benchmark-macro-junit4/1.5.0-SNAPSHOT/benchmark-macro-junit4-1.5.0-20260228.045313-1.aar
 Version: 1.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_traceprocessor_android/README.chromium b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_traceprocessor_android/README.chromium
index 3e2fbccd..859913c 100644
--- a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_traceprocessor_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_traceprocessor_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Benchmark TraceProcessor
 Short Name: benchmark-traceprocessor-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/benchmark/benchmark-traceprocessor-android/1.5.0-SNAPSHOT/benchmark-traceprocessor-android-1.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/benchmark/benchmark-traceprocessor-android/1.5.0-SNAPSHOT/benchmark-traceprocessor-android-1.5.0-20260228.045313-1.aar
 Version: 1.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_biometric_biometric/README.chromium b/third_party/androidx/committed/libs/androidx_biometric_biometric/README.chromium
index 8e440cbb..306750a 100644
--- a/third_party/androidx/committed/libs/androidx_biometric_biometric/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_biometric_biometric/README.chromium
@@ -1,6 +1,6 @@
 Name: Biometric
 Short Name: biometric
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/biometric/biometric/1.4.0-SNAPSHOT/biometric-1.4.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/biometric/biometric/1.4.0-SNAPSHOT/biometric-1.4.0-20260228.045313-1.aar
 Version: 1.4.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_browser_browser/README.chromium b/third_party/androidx/committed/libs/androidx_browser_browser/README.chromium
index 13d72ad20..ad5454d 100644
--- a/third_party/androidx/committed/libs/androidx_browser_browser/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_browser_browser/README.chromium
@@ -1,6 +1,6 @@
 Name: Browser
 Short Name: browser
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/browser/browser/1.10.0-SNAPSHOT/browser-1.10.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/browser/browser/1.10.0-SNAPSHOT/browser-1.10.0-20260228.045313-1.aar
 Version: 1.10.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_cardview_cardview/README.chromium b/third_party/androidx/committed/libs/androidx_cardview_cardview/README.chromium
index 20c9eb6..ab08c30 100644
--- a/third_party/androidx/committed/libs/androidx_cardview_cardview/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_cardview_cardview/README.chromium
@@ -1,6 +1,6 @@
 Name: CardView
 Short Name: cardview
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/cardview/cardview/1.1.0-SNAPSHOT/cardview-1.1.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/cardview/cardview/1.1.0-SNAPSHOT/cardview-1.1.0-20260228.045313-1.aar
 Version: 1.1.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_collection_collection_jvm/README.chromium b/third_party/androidx/committed/libs/androidx_collection_collection_jvm/README.chromium
index da42ea0..356cf076 100644
--- a/third_party/androidx/committed/libs/androidx_collection_collection_jvm/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_collection_collection_jvm/README.chromium
@@ -1,6 +1,6 @@
 Name: collections
 Short Name: collection-jvm
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/collection/collection-jvm/1.7.0-SNAPSHOT/collection-jvm-1.7.0-20260227.122158-1.jar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/collection/collection-jvm/1.7.0-SNAPSHOT/collection-jvm-1.7.0-20260228.045313-1.jar
 Version: 1.7.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_collection_collection_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_collection_collection_ktx/README.chromium
index 3acb277..47edfa8 100644
--- a/third_party/androidx/committed/libs/androidx_collection_collection_ktx/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_collection_collection_ktx/README.chromium
@@ -1,6 +1,6 @@
 Name: Collections Kotlin Extensions
 Short Name: collection-ktx
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/collection/collection-ktx/1.7.0-SNAPSHOT/collection-ktx-1.7.0-20260227.122158-1.jar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/collection/collection-ktx/1.7.0-SNAPSHOT/collection-ktx-1.7.0-20260228.045313-1.jar
 Version: 1.7.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_animation_animation_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_animation_animation_android/README.chromium
index 1668dcd..9fc8186a 100644
--- a/third_party/androidx/committed/libs/androidx_compose_animation_animation_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_animation_animation_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Animation
 Short Name: animation-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/animation/animation-android/1.11.0-SNAPSHOT/animation-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/animation/animation-android/1.11.0-SNAPSHOT/animation-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_animation_animation_core_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_animation_animation_core_android/README.chromium
index 2f6ad2d..89754907 100644
--- a/third_party/androidx/committed/libs/androidx_compose_animation_animation_core_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_animation_animation_core_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Animation Core
 Short Name: animation-core-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/animation/animation-core-android/1.11.0-SNAPSHOT/animation-core-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/animation/animation-core-android/1.11.0-SNAPSHOT/animation-core-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_android/README.chromium
index 3d4a433d..d73075a 100644
--- a/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Foundation
 Short Name: foundation-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/foundation/foundation-android/1.11.0-SNAPSHOT/foundation-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/foundation/foundation-android/1.11.0-SNAPSHOT/foundation-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_layout_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_layout_android/README.chromium
index 9fd03c46..c48ebcc 100644
--- a/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_layout_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_layout_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Layouts
 Short Name: foundation-layout-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/foundation/foundation-layout-android/1.11.0-SNAPSHOT/foundation-layout-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/foundation/foundation-layout-android/1.11.0-SNAPSHOT/foundation-layout-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_material3_material3_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_material3_material3_android/README.chromium
index 1e2ac5f..dea727e 100644
--- a/third_party/androidx/committed/libs/androidx_compose_material3_material3_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_material3_material3_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Material3 Components
 Short Name: material3-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/material3/material3-android/1.5.0-SNAPSHOT/material3-android-1.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/material3/material3-android/1.5.0-SNAPSHOT/material3-android-1.5.0-20260228.045313-1.aar
 Version: 1.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_material_material_ripple_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_material_material_ripple_android/README.chromium
index 689c0da..94d319f 100644
--- a/third_party/androidx/committed/libs/androidx_compose_material_material_ripple_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_material_material_ripple_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Material Ripple
 Short Name: material-ripple-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/material/material-ripple-android/1.11.0-SNAPSHOT/material-ripple-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/material/material-ripple-android/1.11.0-SNAPSHOT/material-ripple-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_android/README.chromium
index a7118e6..a49c339 100644
--- a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Runtime
 Short Name: runtime-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/runtime/runtime-android/1.11.0-SNAPSHOT/runtime-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/runtime/runtime-android/1.11.0-SNAPSHOT/runtime-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_annotation_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_annotation_android/README.chromium
index c0b7f16..fab59f4 100644
--- a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_annotation_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_annotation_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Runtime Annotation
 Short Name: runtime-annotation-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/runtime/runtime-annotation-android/1.11.0-SNAPSHOT/runtime-annotation-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/runtime/runtime-annotation-android/1.11.0-SNAPSHOT/runtime-annotation-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_retain_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_retain_android/README.chromium
index 7697627f..f23368e 100644
--- a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_retain_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_retain_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Runtime Retain
 Short Name: runtime-retain-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/runtime/runtime-retain-android/1.11.0-SNAPSHOT/runtime-retain-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/runtime/runtime-retain-android/1.11.0-SNAPSHOT/runtime-retain-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_saveable_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_saveable_android/README.chromium
index 91bb68f0..7c62d07 100644
--- a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_saveable_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_saveable_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Saveable
 Short Name: runtime-saveable-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/runtime/runtime-saveable-android/1.11.0-SNAPSHOT/runtime-saveable-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/runtime/runtime-saveable-android/1.11.0-SNAPSHOT/runtime-saveable-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_android/README.chromium
index 7063795..10ced93f 100644
--- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose UI
 Short Name: ui-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/ui/ui-android/1.11.0-SNAPSHOT/ui-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/ui/ui-android/1.11.0-SNAPSHOT/ui-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_geometry_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_geometry_android/README.chromium
index d4822ad..3483c38 100644
--- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_geometry_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_geometry_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Geometry
 Short Name: ui-geometry-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/ui/ui-geometry-android/1.11.0-SNAPSHOT/ui-geometry-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/ui/ui-geometry-android/1.11.0-SNAPSHOT/ui-geometry-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_graphics_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_graphics_android/README.chromium
index 66652560..cc9f219 100644
--- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_graphics_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_graphics_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Graphics
 Short Name: ui-graphics-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/ui/ui-graphics-android/1.11.0-SNAPSHOT/ui-graphics-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/ui/ui-graphics-android/1.11.0-SNAPSHOT/ui-graphics-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_android/README.chromium
index c03b53f1..f7a6a92 100644
--- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Testing
 Short Name: ui-test-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/ui/ui-test-android/1.11.0-SNAPSHOT/ui-test-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/ui/ui-test-android/1.11.0-SNAPSHOT/ui-test-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_junit4_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_junit4_android/README.chromium
index ed93d65..c0c5b026 100644
--- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_junit4_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_junit4_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Testing for JUnit4
 Short Name: ui-test-junit4-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/ui/ui-test-junit4-android/1.11.0-SNAPSHOT/ui-test-junit4-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/ui/ui-test-junit4-android/1.11.0-SNAPSHOT/ui-test-junit4-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_manifest/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_manifest/README.chromium
index 96d3903..2bb0892 100644
--- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_manifest/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_manifest/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Testing manifest dependency
 Short Name: ui-test-manifest
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/ui/ui-test-manifest/1.11.0-SNAPSHOT/ui-test-manifest-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/ui/ui-test-manifest/1.11.0-SNAPSHOT/ui-test-manifest-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_android/README.chromium
index f223d8d..d7ae82a 100644
--- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose UI Text
 Short Name: ui-text-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/ui/ui-text-android/1.11.0-SNAPSHOT/ui-text-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/ui/ui-text-android/1.11.0-SNAPSHOT/ui-text-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_google_fonts/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_google_fonts/README.chromium
index 7855d4f..ac8532b 100644
--- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_google_fonts/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_google_fonts/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Google Fonts integration
 Short Name: ui-text-google-fonts
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/ui/ui-text-google-fonts/1.11.0-SNAPSHOT/ui-text-google-fonts-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/ui/ui-text-google-fonts/1.11.0-SNAPSHOT/ui-text-google-fonts-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_unit_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_unit_android/README.chromium
index b2ffbbd..7adf65c 100644
--- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_unit_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_unit_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Unit
 Short Name: ui-unit-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/ui/ui-unit-android/1.11.0-SNAPSHOT/ui-unit-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/ui/ui-unit-android/1.11.0-SNAPSHOT/ui-unit-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_util_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_util_android/README.chromium
index 1470789..5054894 100644
--- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_util_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_util_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Util
 Short Name: ui-util-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/compose/ui/ui-util-android/1.11.0-SNAPSHOT/ui-util-android-1.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/compose/ui/ui-util-android/1.11.0-SNAPSHOT/ui-util-android-1.11.0-20260228.045313-1.aar
 Version: 1.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout/README.chromium b/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout/README.chromium
index 3ddc83b..350ead0 100644
--- a/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout/README.chromium
@@ -1,6 +1,6 @@
 Name: ConstraintLayout
 Short Name: constraintlayout
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/constraintlayout/constraintlayout/2.3.0-SNAPSHOT/constraintlayout-2.3.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/constraintlayout/constraintlayout/2.3.0-SNAPSHOT/constraintlayout-2.3.0-20260228.045313-1.aar
 Version: 2.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout_core/README.chromium b/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout_core/README.chromium
index afa1dca..3c44543 100644
--- a/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout_core/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout_core/README.chromium
@@ -1,6 +1,6 @@
 Name: ConstraintLayout Core
 Short Name: constraintlayout-core
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/constraintlayout/constraintlayout-core/1.2.0-SNAPSHOT/constraintlayout-core-1.2.0-20260227.122158-1.jar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/constraintlayout/constraintlayout-core/1.2.0-SNAPSHOT/constraintlayout-core-1.2.0-20260228.045313-1.jar
 Version: 1.2.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_core_core/README.chromium b/third_party/androidx/committed/libs/androidx_core_core/README.chromium
index caae503b..3d3b7fb 100644
--- a/third_party/androidx/committed/libs/androidx_core_core/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_core_core/README.chromium
@@ -1,6 +1,6 @@
 Name: Core
 Short Name: core
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/core/core/1.18.0-SNAPSHOT/core-1.18.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/core/core/1.18.0-SNAPSHOT/core-1.18.0-20260228.045313-1.aar
 Version: 1.18.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_core_core_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_core_core_ktx/README.chromium
index efefdb91..5c0b23c0d 100644
--- a/third_party/androidx/committed/libs/androidx_core_core_ktx/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_core_core_ktx/README.chromium
@@ -1,6 +1,6 @@
 Name: Core Kotlin Extensions
 Short Name: core-ktx
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/core/core-ktx/1.18.0-SNAPSHOT/core-ktx-1.18.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/core/core-ktx/1.18.0-SNAPSHOT/core-ktx-1.18.0-20260228.045313-1.aar
 Version: 1.18.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_core_core_pip/README.chromium b/third_party/androidx/committed/libs/androidx_core_core_pip/README.chromium
index 56e74468..69ee525 100644
--- a/third_party/androidx/committed/libs/androidx_core_core_pip/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_core_core_pip/README.chromium
@@ -1,6 +1,6 @@
 Name: androidx.core:core-pip
 Short Name: core-pip
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/core/core-pip/1.0.0-SNAPSHOT/core-pip-1.0.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/core/core-pip/1.0.0-SNAPSHOT/core-pip-1.0.0-20260228.045313-1.aar
 Version: 1.0.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_core_core_viewtree/README.chromium b/third_party/androidx/committed/libs/androidx_core_core_viewtree/README.chromium
index f647233..ac2083c 100644
--- a/third_party/androidx/committed/libs/androidx_core_core_viewtree/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_core_core_viewtree/README.chromium
@@ -1,6 +1,6 @@
 Name: androidx.core:core-viewtree
 Short Name: core-viewtree
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/core/core-viewtree/1.1.0-SNAPSHOT/core-viewtree-1.1.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/core/core-viewtree/1.1.0-SNAPSHOT/core-viewtree-1.1.0-20260228.045313-1.aar
 Version: 1.1.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_credentials_credentials/README.chromium b/third_party/androidx/committed/libs/androidx_credentials_credentials/README.chromium
index dd447113..718feab4 100644
--- a/third_party/androidx/committed/libs/androidx_credentials_credentials/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_credentials_credentials/README.chromium
@@ -1,6 +1,6 @@
 Name: Credentials
 Short Name: credentials
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/credentials/credentials/1.6.0-SNAPSHOT/credentials-1.6.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/credentials/credentials/1.6.0-SNAPSHOT/credentials-1.6.0-20260228.045313-1.aar
 Version: 1.6.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_credentials_credentials_play_services_auth/README.chromium b/third_party/androidx/committed/libs/androidx_credentials_credentials_play_services_auth/README.chromium
index a511858..214ef58 100644
--- a/third_party/androidx/committed/libs/androidx_credentials_credentials_play_services_auth/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_credentials_credentials_play_services_auth/README.chromium
@@ -1,6 +1,6 @@
 Name: Credentials Play Services Auth
 Short Name: credentials-play-services-auth
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/credentials/credentials-play-services-auth/1.6.0-SNAPSHOT/credentials-play-services-auth-1.6.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/credentials/credentials-play-services-auth/1.6.0-SNAPSHOT/credentials-play-services-auth-1.6.0-20260228.045313-1.aar
 Version: 1.6.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider/README.chromium b/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider/README.chromium
index 2463da5..fd8aecc2 100644
--- a/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider/README.chromium
@@ -1,6 +1,6 @@
 Name: androidx.credentials.registry:registry-provider
 Short Name: registry-provider
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/credentials/registry/registry-provider/1.0.0-SNAPSHOT/registry-provider-1.0.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/credentials/registry/registry-provider/1.0.0-SNAPSHOT/registry-provider-1.0.0-20260228.045313-1.aar
 Version: 1.0.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider_play_services/README.chromium b/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider_play_services/README.chromium
index ce83cc9f..5ca0919 100644
--- a/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider_play_services/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider_play_services/README.chromium
@@ -1,6 +1,6 @@
 Name: androidx.credentials.registry:registry-provider-play-services
 Short Name: registry-provider-play-services
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/credentials/registry/registry-provider-play-services/1.0.0-SNAPSHOT/registry-provider-play-services-1.0.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/credentials/registry/registry-provider-play-services/1.0.0-SNAPSHOT/registry-provider-play-services-1.0.0-20260228.045313-1.aar
 Version: 1.0.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_cursoradapter_cursoradapter/README.chromium b/third_party/androidx/committed/libs/androidx_cursoradapter_cursoradapter/README.chromium
index fe8d9088..e5cc1f42 100644
--- a/third_party/androidx/committed/libs/androidx_cursoradapter_cursoradapter/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_cursoradapter_cursoradapter/README.chromium
@@ -1,6 +1,6 @@
 Name: Cursor Adapter
 Short Name: cursoradapter
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/cursoradapter/cursoradapter/1.1.0-SNAPSHOT/cursoradapter-1.1.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/cursoradapter/cursoradapter/1.1.0-SNAPSHOT/cursoradapter-1.1.0-20260228.045313-1.aar
 Version: 1.1.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_android/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_android/README.chromium
index 16ab248..6024d19 100644
--- a/third_party/androidx/committed/libs/androidx_datastore_datastore_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_android/README.chromium
@@ -1,6 +1,6 @@
 Name: DataStore
 Short Name: datastore-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/datastore/datastore-android/1.3.0-SNAPSHOT/datastore-android-1.3.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/datastore/datastore-android/1.3.0-SNAPSHOT/datastore-android-1.3.0-20260228.045313-1.aar
 Version: 1.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_core_android/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_core_android/README.chromium
index cc6f0efa..96eefed 100644
--- a/third_party/androidx/committed/libs/androidx_datastore_datastore_core_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_core_android/README.chromium
@@ -1,6 +1,6 @@
 Name: DataStore Core
 Short Name: datastore-core-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/datastore/datastore-core-android/1.3.0-SNAPSHOT/datastore-core-android-1.3.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/datastore/datastore-core-android/1.3.0-SNAPSHOT/datastore-core-android-1.3.0-20260228.045313-1.aar
 Version: 1.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_core_okio_jvm/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_core_okio_jvm/README.chromium
index dc14eae2..2fb4a98c 100644
--- a/third_party/androidx/committed/libs/androidx_datastore_datastore_core_okio_jvm/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_core_okio_jvm/README.chromium
@@ -1,6 +1,6 @@
 Name: DataStore Core Okio
 Short Name: datastore-core-okio-jvm
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/datastore/datastore-core-okio-jvm/1.3.0-SNAPSHOT/datastore-core-okio-jvm-1.3.0-20260227.122158-1.jar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/datastore/datastore-core-okio-jvm/1.3.0-SNAPSHOT/datastore-core-okio-jvm-1.3.0-20260228.045313-1.jar
 Version: 1.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_android/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_android/README.chromium
index 34c41b6..c2c000ad 100644
--- a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Preferences DataStore
 Short Name: datastore-preferences-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/datastore/datastore-preferences-android/1.3.0-SNAPSHOT/datastore-preferences-android-1.3.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/datastore/datastore-preferences-android/1.3.0-SNAPSHOT/datastore-preferences-android-1.3.0-20260228.045313-1.aar
 Version: 1.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_core_android/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_core_android/README.chromium
index 037aeb5c..f4c4f461 100644
--- a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_core_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_core_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Preferences DataStore Core
 Short Name: datastore-preferences-core-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/datastore/datastore-preferences-core-android/1.3.0-SNAPSHOT/datastore-preferences-core-android-1.3.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/datastore/datastore-preferences-core-android/1.3.0-SNAPSHOT/datastore-preferences-core-android-1.3.0-20260228.045313-1.aar
 Version: 1.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_external_protobuf/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_external_protobuf/README.chromium
index 62024a5..bfde8b8d 100644
--- a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_external_protobuf/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_external_protobuf/README.chromium
@@ -1,6 +1,6 @@
 Name: Preferences External Protobuf
 Short Name: datastore-preferences-external-protobuf
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/datastore/datastore-preferences-external-protobuf/1.3.0-SNAPSHOT/datastore-preferences-external-protobuf-1.3.0-20260227.122158-1.jar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/datastore/datastore-preferences-external-protobuf/1.3.0-SNAPSHOT/datastore-preferences-external-protobuf-1.3.0-20260228.045313-1.jar
 Version: 1.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: BSD-3-Clause
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_proto/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_proto/README.chromium
index a329370a..79ab4f5 100644
--- a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_proto/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_proto/README.chromium
@@ -1,6 +1,6 @@
 Name: Preferences DataStore Proto
 Short Name: datastore-preferences-proto
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/datastore/datastore-preferences-proto/1.3.0-SNAPSHOT/datastore-preferences-proto-1.3.0-20260227.122158-1.jar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/datastore/datastore-preferences-proto/1.3.0-SNAPSHOT/datastore-preferences-proto-1.3.0-20260228.045313-1.jar
 Version: 1.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_drawerlayout_drawerlayout/README.chromium b/third_party/androidx/committed/libs/androidx_drawerlayout_drawerlayout/README.chromium
index 1d0e26d9..dde5a7a37 100644
--- a/third_party/androidx/committed/libs/androidx_drawerlayout_drawerlayout/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_drawerlayout_drawerlayout/README.chromium
@@ -1,6 +1,6 @@
 Name: Drawer Layout
 Short Name: drawerlayout
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/drawerlayout/drawerlayout/1.3.0-SNAPSHOT/drawerlayout-1.3.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/drawerlayout/drawerlayout/1.3.0-SNAPSHOT/drawerlayout-1.3.0-20260228.045313-1.aar
 Version: 1.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_fragment_fragment/README.chromium b/third_party/androidx/committed/libs/androidx_fragment_fragment/README.chromium
index 473d8ff..d8056f83 100644
--- a/third_party/androidx/committed/libs/androidx_fragment_fragment/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_fragment_fragment/README.chromium
@@ -1,6 +1,6 @@
 Name: fragment
 Short Name: fragment
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/fragment/fragment/1.9.0-SNAPSHOT/fragment-1.9.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/fragment/fragment/1.9.0-SNAPSHOT/fragment-1.9.0-20260228.045313-1.aar
 Version: 1.9.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_fragment_fragment_compose/README.chromium b/third_party/androidx/committed/libs/androidx_fragment_fragment_compose/README.chromium
index cb194aa5..f763c20 100644
--- a/third_party/androidx/committed/libs/androidx_fragment_fragment_compose/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_fragment_fragment_compose/README.chromium
@@ -1,6 +1,6 @@
 Name: Fragment Compose
 Short Name: fragment-compose
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/fragment/fragment-compose/1.9.0-SNAPSHOT/fragment-compose-1.9.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/fragment/fragment-compose/1.9.0-SNAPSHOT/fragment-compose-1.9.0-20260228.045313-1.aar
 Version: 1.9.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_fragment_fragment_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_fragment_fragment_ktx/README.chromium
index 8faf422..ad53655 100644
--- a/third_party/androidx/committed/libs/androidx_fragment_fragment_ktx/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_fragment_fragment_ktx/README.chromium
@@ -1,6 +1,6 @@
 Name: Fragment Kotlin Extensions
 Short Name: fragment-ktx
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/fragment/fragment-ktx/1.9.0-SNAPSHOT/fragment-ktx-1.9.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/fragment/fragment-ktx/1.9.0-SNAPSHOT/fragment-ktx-1.9.0-20260228.045313-1.aar
 Version: 1.9.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_fragment_fragment_testing/README.chromium b/third_party/androidx/committed/libs/androidx_fragment_fragment_testing/README.chromium
index b98ad64..bc0a72f 100644
--- a/third_party/androidx/committed/libs/androidx_fragment_fragment_testing/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_fragment_fragment_testing/README.chromium
@@ -1,6 +1,6 @@
 Name: Fragment Testing Extensions
 Short Name: fragment-testing
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/fragment/fragment-testing/1.9.0-SNAPSHOT/fragment-testing-1.9.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/fragment/fragment-testing/1.9.0-SNAPSHOT/fragment-testing-1.9.0-20260228.045313-1.aar
 Version: 1.9.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_fragment_fragment_testing_manifest/README.chromium b/third_party/androidx/committed/libs/androidx_fragment_fragment_testing_manifest/README.chromium
index 0c90116..9042eaf 100644
--- a/third_party/androidx/committed/libs/androidx_fragment_fragment_testing_manifest/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_fragment_fragment_testing_manifest/README.chromium
@@ -1,6 +1,6 @@
 Name: Fragment Testing Manifest dependency
 Short Name: fragment-testing-manifest
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/fragment/fragment-testing-manifest/1.9.0-SNAPSHOT/fragment-testing-manifest-1.9.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/fragment/fragment-testing-manifest/1.9.0-SNAPSHOT/fragment-testing-manifest-1.9.0-20260228.045313-1.aar
 Version: 1.9.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_graphics_graphics_path/README.chromium b/third_party/androidx/committed/libs/androidx_graphics_graphics_path/README.chromium
index ed01997..1b5e113b 100644
--- a/third_party/androidx/committed/libs/androidx_graphics_graphics_path/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_graphics_graphics_path/README.chromium
@@ -1,6 +1,6 @@
 Name: Android Graphics Path
 Short Name: graphics-path
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/graphics/graphics-path/1.1.0-SNAPSHOT/graphics-path-1.1.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/graphics/graphics-path/1.1.0-SNAPSHOT/graphics-path-1.1.0-20260228.045313-1.aar
 Version: 1.1.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_interpolator_interpolator/README.chromium b/third_party/androidx/committed/libs/androidx_interpolator_interpolator/README.chromium
index 169aa59..63c96de 100644
--- a/third_party/androidx/committed/libs/androidx_interpolator_interpolator/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_interpolator_interpolator/README.chromium
@@ -1,6 +1,6 @@
 Name: Interpolators
 Short Name: interpolator
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/interpolator/interpolator/1.1.0-SNAPSHOT/interpolator-1.1.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/interpolator/interpolator/1.1.0-SNAPSHOT/interpolator-1.1.0-20260228.045313-1.aar
 Version: 1.1.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_leanback_leanback/README.chromium b/third_party/androidx/committed/libs/androidx_leanback_leanback/README.chromium
index b5073d75..3b65f4c 100644
--- a/third_party/androidx/committed/libs/androidx_leanback_leanback/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_leanback_leanback/README.chromium
@@ -1,6 +1,6 @@
 Name: Leanback
 Short Name: leanback
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/leanback/leanback/1.3.0-SNAPSHOT/leanback-1.3.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/leanback/leanback/1.3.0-SNAPSHOT/leanback-1.3.0-20260228.045313-1.aar
 Version: 1.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_leanback_leanback_grid/README.chromium b/third_party/androidx/committed/libs/androidx_leanback_leanback_grid/README.chromium
index 32a5abe1..653e224 100644
--- a/third_party/androidx/committed/libs/androidx_leanback_leanback_grid/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_leanback_leanback_grid/README.chromium
@@ -1,6 +1,6 @@
 Name: Leanback Grid
 Short Name: leanback-grid
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/leanback/leanback-grid/1.1.0-SNAPSHOT/leanback-grid-1.1.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/leanback/leanback-grid/1.1.0-SNAPSHOT/leanback-grid-1.1.0-20260228.045313-1.aar
 Version: 1.1.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_java8/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_java8/README.chromium
index d0fbfe5..a5c188b 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_java8/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_java8/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle-Common for Java 8
 Short Name: lifecycle-common-java8
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-common-java8/2.11.0-SNAPSHOT/lifecycle-common-java8-2.11.0-20260227.122158-1.jar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-common-java8/2.11.0-SNAPSHOT/lifecycle-common-java8-2.11.0-20260228.045313-1.jar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_jvm/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_jvm/README.chromium
index 4772469..64b35f4 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_jvm/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_jvm/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle-Common
 Short Name: lifecycle-common-jvm
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-common-jvm/2.11.0-SNAPSHOT/lifecycle-common-jvm-2.11.0-20260227.122158-1.jar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-common-jvm/2.11.0-SNAPSHOT/lifecycle-common-jvm-2.11.0-20260228.045313-1.jar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata/README.chromium
index 202c6cba..681af8b 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle LiveData
 Short Name: lifecycle-livedata
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-livedata/2.11.0-SNAPSHOT/lifecycle-livedata-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-livedata/2.11.0-SNAPSHOT/lifecycle-livedata-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core/README.chromium
index 583b971f..c237bd6 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle LiveData Core
 Short Name: lifecycle-livedata-core
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-livedata-core/2.11.0-SNAPSHOT/lifecycle-livedata-core-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-livedata-core/2.11.0-SNAPSHOT/lifecycle-livedata-core-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core_ktx/README.chromium
index b35ccd6..adc9ca3 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core_ktx/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core_ktx/README.chromium
@@ -1,6 +1,6 @@
 Name: LiveData Core Kotlin Extensions
 Short Name: lifecycle-livedata-core-ktx
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-livedata-core-ktx/2.11.0-SNAPSHOT/lifecycle-livedata-core-ktx-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-livedata-core-ktx/2.11.0-SNAPSHOT/lifecycle-livedata-core-ktx-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_ktx/README.chromium
index 8f24fa2..ab75327 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_ktx/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_ktx/README.chromium
@@ -1,6 +1,6 @@
 Name: LiveData Kotlin Extensions
 Short Name: lifecycle-livedata-ktx
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-livedata-ktx/2.11.0-SNAPSHOT/lifecycle-livedata-ktx-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-livedata-ktx/2.11.0-SNAPSHOT/lifecycle-livedata-ktx-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_process/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_process/README.chromium
index a2b65c1d..f5638c7a9 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_process/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_process/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle Process
 Short Name: lifecycle-process
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-process/2.11.0-SNAPSHOT/lifecycle-process-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-process/2.11.0-SNAPSHOT/lifecycle-process-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_android/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_android/README.chromium
index 1b28cca..535f0eb 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle Runtime
 Short Name: lifecycle-runtime-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-runtime-android/2.11.0-SNAPSHOT/lifecycle-runtime-android-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-runtime-android/2.11.0-SNAPSHOT/lifecycle-runtime-android-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_compose_android/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_compose_android/README.chromium
index ad1a4b52..d9a89df6 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_compose_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_compose_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle Runtime Compose
 Short Name: lifecycle-runtime-compose-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-runtime-compose-android/2.11.0-SNAPSHOT/lifecycle-runtime-compose-android-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-runtime-compose-android/2.11.0-SNAPSHOT/lifecycle-runtime-compose-android-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_ktx_android/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_ktx_android/README.chromium
index f5e3cc9..6301658 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_ktx_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_ktx_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle Kotlin Extensions
 Short Name: lifecycle-runtime-ktx-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-runtime-ktx-android/2.11.0-SNAPSHOT/lifecycle-runtime-ktx-android-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-runtime-ktx-android/2.11.0-SNAPSHOT/lifecycle-runtime-ktx-android-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_service/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_service/README.chromium
index 517e968..8a7adb56 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_service/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_service/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle Service
 Short Name: lifecycle-service
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-service/2.11.0-SNAPSHOT/lifecycle-service-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-service/2.11.0-SNAPSHOT/lifecycle-service-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_android/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_android/README.chromium
index 4928c26..bb95e37 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle ViewModel
 Short Name: lifecycle-viewmodel-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-android/2.11.0-SNAPSHOT/lifecycle-viewmodel-android-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-android/2.11.0-SNAPSHOT/lifecycle-viewmodel-android-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_compose_android/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_compose_android/README.chromium
index f9836a0..0d8d9b3 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_compose_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_compose_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle ViewModel Compose
 Short Name: lifecycle-viewmodel-compose-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-compose-android/2.11.0-SNAPSHOT/lifecycle-viewmodel-compose-android-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-compose-android/2.11.0-SNAPSHOT/lifecycle-viewmodel-compose-android-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_ktx/README.chromium
index a3f585f..4fb2963 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_ktx/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_ktx/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle ViewModel Kotlin Extensions
 Short Name: lifecycle-viewmodel-ktx
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-ktx/2.11.0-SNAPSHOT/lifecycle-viewmodel-ktx-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-ktx/2.11.0-SNAPSHOT/lifecycle-viewmodel-ktx-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_savedstate_android/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_savedstate_android/README.chromium
index 5bbcbaa3..5d82b251 100644
--- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_savedstate_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_savedstate_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Lifecycle ViewModel with SavedState
 Short Name: lifecycle-viewmodel-savedstate-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-savedstate-android/2.11.0-SNAPSHOT/lifecycle-viewmodel-savedstate-android-2.11.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-savedstate-android/2.11.0-SNAPSHOT/lifecycle-viewmodel-savedstate-android-2.11.0-20260228.045313-1.aar
 Version: 2.11.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_loader_loader/README.chromium b/third_party/androidx/committed/libs/androidx_loader_loader/README.chromium
index ce136979..2100716 100644
--- a/third_party/androidx/committed/libs/androidx_loader_loader/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_loader_loader/README.chromium
@@ -1,6 +1,6 @@
 Name: loader
 Short Name: loader
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/loader/loader/1.2.0-SNAPSHOT/loader-1.2.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/loader/loader/1.2.0-SNAPSHOT/loader-1.2.0-20260228.045313-1.aar
 Version: 1.2.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_media_media/README.chromium b/third_party/androidx/committed/libs/androidx_media_media/README.chromium
index c1f0040..62c711af 100644
--- a/third_party/androidx/committed/libs/androidx_media_media/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_media_media/README.chromium
@@ -1,6 +1,6 @@
 Name: Media
 Short Name: media
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/media/media/1.8.0-SNAPSHOT/media-1.8.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/media/media/1.8.0-SNAPSHOT/media-1.8.0-20260228.045313-1.aar
 Version: 1.8.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_mediarouter_mediarouter/README.chromium b/third_party/androidx/committed/libs/androidx_mediarouter_mediarouter/README.chromium
index dedc432..363338b 100644
--- a/third_party/androidx/committed/libs/androidx_mediarouter_mediarouter/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_mediarouter_mediarouter/README.chromium
@@ -1,6 +1,6 @@
 Name: MediaRouter
 Short Name: mediarouter
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/mediarouter/mediarouter/1.9.0-SNAPSHOT/mediarouter-1.9.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/mediarouter/mediarouter/1.9.0-SNAPSHOT/mediarouter-1.9.0-20260228.045313-1.aar
 Version: 1.9.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_navigation_navigation_common_android/README.chromium b/third_party/androidx/committed/libs/androidx_navigation_navigation_common_android/README.chromium
index 8aa9f12..7d1f8047 100644
--- a/third_party/androidx/committed/libs/androidx_navigation_navigation_common_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_navigation_navigation_common_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Navigation Common
 Short Name: navigation-common-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/navigation/navigation-common-android/2.10.0-SNAPSHOT/navigation-common-android-2.10.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/navigation/navigation-common-android/2.10.0-SNAPSHOT/navigation-common-android-2.10.0-20260228.045313-1.aar
 Version: 2.10.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_navigation_navigation_compose_android/README.chromium b/third_party/androidx/committed/libs/androidx_navigation_navigation_compose_android/README.chromium
index cb089de..2b2df070 100644
--- a/third_party/androidx/committed/libs/androidx_navigation_navigation_compose_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_navigation_navigation_compose_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Compose Navigation
 Short Name: navigation-compose-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/navigation/navigation-compose-android/2.10.0-SNAPSHOT/navigation-compose-android-2.10.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/navigation/navigation-compose-android/2.10.0-SNAPSHOT/navigation-compose-android-2.10.0-20260228.045313-1.aar
 Version: 2.10.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_navigation_navigation_runtime_android/README.chromium b/third_party/androidx/committed/libs/androidx_navigation_navigation_runtime_android/README.chromium
index 1d4ec806..1c753f3a 100644
--- a/third_party/androidx/committed/libs/androidx_navigation_navigation_runtime_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_navigation_navigation_runtime_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Navigation Runtime
 Short Name: navigation-runtime-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/navigation/navigation-runtime-android/2.10.0-SNAPSHOT/navigation-runtime-android-2.10.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/navigation/navigation-runtime-android/2.10.0-SNAPSHOT/navigation-runtime-android-2.10.0-20260228.045313-1.aar
 Version: 2.10.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_android/README.chromium b/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_android/README.chromium
index f8950dc..aaeff09f 100644
--- a/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Navigation Event
 Short Name: navigationevent-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/navigationevent/navigationevent-android/1.1.0-SNAPSHOT/navigationevent-android-1.1.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/navigationevent/navigationevent-android/1.1.0-SNAPSHOT/navigationevent-android-1.1.0-20260228.045313-1.aar
 Version: 1.1.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_compose_android/README.chromium b/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_compose_android/README.chromium
index 966de4c..2b98662 100644
--- a/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_compose_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_compose_android/README.chromium
@@ -1,6 +1,6 @@
 Name: NavigationEvent Compose
 Short Name: navigationevent-compose-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/navigationevent/navigationevent-compose-android/1.1.0-SNAPSHOT/navigationevent-compose-android-1.1.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/navigationevent/navigationevent-compose-android/1.1.0-SNAPSHOT/navigationevent-compose-android-1.1.0-20260228.045313-1.aar
 Version: 1.1.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_paging_paging_common_android/README.chromium b/third_party/androidx/committed/libs/androidx_paging_paging_common_android/README.chromium
index 2515271..e8c4f0d 100644
--- a/third_party/androidx/committed/libs/androidx_paging_paging_common_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_paging_paging_common_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Paging-Common
 Short Name: paging-common-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/paging/paging-common-android/3.5.0-SNAPSHOT/paging-common-android-3.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/paging/paging-common-android/3.5.0-SNAPSHOT/paging-common-android-3.5.0-20260228.045313-1.aar
 Version: 3.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_paging_paging_common_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_paging_paging_common_ktx/README.chromium
index 7c9e09e..35427a5 100644
--- a/third_party/androidx/committed/libs/androidx_paging_paging_common_ktx/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_paging_paging_common_ktx/README.chromium
@@ -1,6 +1,6 @@
 Name: Paging-Common Kotlin Extensions
 Short Name: paging-common-ktx
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/paging/paging-common-ktx/3.5.0-SNAPSHOT/paging-common-ktx-3.5.0-20260227.122158-1.jar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/paging/paging-common-ktx/3.5.0-SNAPSHOT/paging-common-ktx-3.5.0-20260228.045313-1.jar
 Version: 3.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_paging_paging_compose_android/README.chromium b/third_party/androidx/committed/libs/androidx_paging_paging_compose_android/README.chromium
index 12a54d7..9c09d95c 100644
--- a/third_party/androidx/committed/libs/androidx_paging_paging_compose_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_paging_paging_compose_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Paging-Compose
 Short Name: paging-compose-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/paging/paging-compose-android/3.5.0-SNAPSHOT/paging-compose-android-3.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/paging/paging-compose-android/3.5.0-SNAPSHOT/paging-compose-android-3.5.0-20260228.045313-1.aar
 Version: 3.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_paging_paging_runtime/README.chromium b/third_party/androidx/committed/libs/androidx_paging_paging_runtime/README.chromium
index 2c36aaa5..71f395ff 100644
--- a/third_party/androidx/committed/libs/androidx_paging_paging_runtime/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_paging_paging_runtime/README.chromium
@@ -1,6 +1,6 @@
 Name: Paging-Runtime
 Short Name: paging-runtime
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/paging/paging-runtime/3.5.0-SNAPSHOT/paging-runtime-3.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/paging/paging-runtime/3.5.0-SNAPSHOT/paging-runtime-3.5.0-20260228.045313-1.aar
 Version: 3.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_palette_palette/README.chromium b/third_party/androidx/committed/libs/androidx_palette_palette/README.chromium
index f4c68ea..7dc4700 100644
--- a/third_party/androidx/committed/libs/androidx_palette_palette/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_palette_palette/README.chromium
@@ -1,6 +1,6 @@
 Name: Palette
 Short Name: palette
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/palette/palette/1.1.0-SNAPSHOT/palette-1.1.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/palette/palette/1.1.0-SNAPSHOT/palette-1.1.0-20260228.045313-1.aar
 Version: 1.1.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_pdf_pdf_document_service/README.chromium b/third_party/androidx/committed/libs/androidx_pdf_pdf_document_service/README.chromium
index 0f321ea..2cd47c6 100644
--- a/third_party/androidx/committed/libs/androidx_pdf_pdf_document_service/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_pdf_pdf_document_service/README.chromium
@@ -1,6 +1,6 @@
 Name: androidx.pdf:pdf-document-service
 Short Name: pdf-document-service
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/pdf/pdf-document-service/1.0.0-SNAPSHOT/pdf-document-service-1.0.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/pdf/pdf-document-service/1.0.0-SNAPSHOT/pdf-document-service-1.0.0-20260228.045313-1.aar
 Version: 1.0.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer/README.chromium b/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer/README.chromium
index cef4650..82998ef 100644
--- a/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer/README.chromium
@@ -1,6 +1,6 @@
 Name: androidx.pdf:pdf-viewer
 Short Name: pdf-viewer
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/pdf/pdf-viewer/1.0.0-SNAPSHOT/pdf-viewer-1.0.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/pdf/pdf-viewer/1.0.0-SNAPSHOT/pdf-viewer-1.0.0-20260228.045313-1.aar
 Version: 1.0.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer_fragment/README.chromium b/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer_fragment/README.chromium
index e544f63..b9350af 100644
--- a/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer_fragment/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer_fragment/README.chromium
@@ -1,6 +1,6 @@
 Name: androidx.pdf:pdf-viewer-fragment
 Short Name: pdf-viewer-fragment
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/pdf/pdf-viewer-fragment/1.0.0-SNAPSHOT/pdf-viewer-fragment-1.0.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/pdf/pdf-viewer-fragment/1.0.0-SNAPSHOT/pdf-viewer-fragment-1.0.0-20260228.045313-1.aar
 Version: 1.0.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_preference_preference/README.chromium b/third_party/androidx/committed/libs/androidx_preference_preference/README.chromium
index aa69d26..3d16d0e 100644
--- a/third_party/androidx/committed/libs/androidx_preference_preference/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_preference_preference/README.chromium
@@ -1,6 +1,6 @@
 Name: Preference
 Short Name: preference
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/preference/preference/1.3.0-SNAPSHOT/preference-1.3.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/preference/preference/1.3.0-SNAPSHOT/preference-1.3.0-20260228.045313-1.aar
 Version: 1.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_profileinstaller_profileinstaller/README.chromium b/third_party/androidx/committed/libs/androidx_profileinstaller_profileinstaller/README.chromium
index ac15252f..9f41d31 100644
--- a/third_party/androidx/committed/libs/androidx_profileinstaller_profileinstaller/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_profileinstaller_profileinstaller/README.chromium
@@ -1,6 +1,6 @@
 Name: Profile Installer
 Short Name: profileinstaller
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/profileinstaller/profileinstaller/1.5.0-SNAPSHOT/profileinstaller-1.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/profileinstaller/profileinstaller/1.5.0-SNAPSHOT/profileinstaller-1.5.0-20260228.045313-1.aar
 Version: 1.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_recyclerview_recyclerview/README.chromium b/third_party/androidx/committed/libs/androidx_recyclerview_recyclerview/README.chromium
index 00f0870..b0d1245 100644
--- a/third_party/androidx/committed/libs/androidx_recyclerview_recyclerview/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_recyclerview_recyclerview/README.chromium
@@ -1,6 +1,6 @@
 Name: RecyclerView
 Short Name: recyclerview
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/recyclerview/recyclerview/1.5.0-SNAPSHOT/recyclerview-1.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/recyclerview/recyclerview/1.5.0-SNAPSHOT/recyclerview-1.5.0-20260228.045313-1.aar
 Version: 1.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_resourceinspection_resourceinspection_annotation/README.chromium b/third_party/androidx/committed/libs/androidx_resourceinspection_resourceinspection_annotation/README.chromium
index dca27a3..d924f06 100644
--- a/third_party/androidx/committed/libs/androidx_resourceinspection_resourceinspection_annotation/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_resourceinspection_resourceinspection_annotation/README.chromium
@@ -1,6 +1,6 @@
 Name: Resource Inspection - Annotations
 Short Name: resourceinspection-annotation
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/resourceinspection/resourceinspection-annotation/1.1.0-SNAPSHOT/resourceinspection-annotation-1.1.0-20260227.122158-1.jar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/resourceinspection/resourceinspection-annotation/1.1.0-SNAPSHOT/resourceinspection-annotation-1.1.0-20260228.045313-1.jar
 Version: 1.1.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_savedstate_savedstate_android/README.chromium b/third_party/androidx/committed/libs/androidx_savedstate_savedstate_android/README.chromium
index fbd24699..483c69c 100644
--- a/third_party/androidx/committed/libs/androidx_savedstate_savedstate_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_savedstate_savedstate_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Saved State
 Short Name: savedstate-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/savedstate/savedstate-android/1.5.0-SNAPSHOT/savedstate-android-1.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/savedstate/savedstate-android/1.5.0-SNAPSHOT/savedstate-android-1.5.0-20260228.045313-1.aar
 Version: 1.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_savedstate_savedstate_compose_android/README.chromium b/third_party/androidx/committed/libs/androidx_savedstate_savedstate_compose_android/README.chromium
index aeb7bcb..86fe907 100644
--- a/third_party/androidx/committed/libs/androidx_savedstate_savedstate_compose_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_savedstate_savedstate_compose_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Saved State Compose
 Short Name: savedstate-compose-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/savedstate/savedstate-compose-android/1.5.0-SNAPSHOT/savedstate-compose-android-1.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/savedstate/savedstate-compose-android/1.5.0-SNAPSHOT/savedstate-compose-android-1.5.0-20260228.045313-1.aar
 Version: 1.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_savedstate_savedstate_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_savedstate_savedstate_ktx/README.chromium
index 6d9a013..2d084eb 100644
--- a/third_party/androidx/committed/libs/androidx_savedstate_savedstate_ktx/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_savedstate_savedstate_ktx/README.chromium
@@ -1,6 +1,6 @@
 Name: SavedState Kotlin Extensions
 Short Name: savedstate-ktx
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/savedstate/savedstate-ktx/1.5.0-SNAPSHOT/savedstate-ktx-1.5.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/savedstate/savedstate-ktx/1.5.0-SNAPSHOT/savedstate-ktx-1.5.0-20260228.045313-1.aar
 Version: 1.5.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_slidingpanelayout_slidingpanelayout/README.chromium b/third_party/androidx/committed/libs/androidx_slidingpanelayout_slidingpanelayout/README.chromium
index aa87749a..ca7adaa7 100644
--- a/third_party/androidx/committed/libs/androidx_slidingpanelayout_slidingpanelayout/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_slidingpanelayout_slidingpanelayout/README.chromium
@@ -1,6 +1,6 @@
 Name: Sliding Pane Layout
 Short Name: slidingpanelayout
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/slidingpanelayout/slidingpanelayout/1.3.0-SNAPSHOT/slidingpanelayout-1.3.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/slidingpanelayout/slidingpanelayout/1.3.0-SNAPSHOT/slidingpanelayout-1.3.0-20260228.045313-1.aar
 Version: 1.3.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_sqlite_sqlite_android/README.chromium b/third_party/androidx/committed/libs/androidx_sqlite_sqlite_android/README.chromium
index 504761f..6c87446 100644
--- a/third_party/androidx/committed/libs/androidx_sqlite_sqlite_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_sqlite_sqlite_android/README.chromium
@@ -1,6 +1,6 @@
 Name: SQLite
 Short Name: sqlite-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/sqlite/sqlite-android/2.7.0-SNAPSHOT/sqlite-android-2.7.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/sqlite/sqlite-android/2.7.0-SNAPSHOT/sqlite-android-2.7.0-20260228.045313-1.aar
 Version: 2.7.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_sqlite_sqlite_framework_android/README.chromium b/third_party/androidx/committed/libs/androidx_sqlite_sqlite_framework_android/README.chromium
index cbf1064..804e7d978 100644
--- a/third_party/androidx/committed/libs/androidx_sqlite_sqlite_framework_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_sqlite_sqlite_framework_android/README.chromium
@@ -1,6 +1,6 @@
 Name: SQLite Framework Integration
 Short Name: sqlite-framework-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/sqlite/sqlite-framework-android/2.7.0-SNAPSHOT/sqlite-framework-android-2.7.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/sqlite/sqlite-framework-android/2.7.0-SNAPSHOT/sqlite-framework-android-2.7.0-20260228.045313-1.aar
 Version: 2.7.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator/README.chromium b/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator/README.chromium
index 433e14d..de7ace30 100644
--- a/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator/README.chromium
@@ -1,6 +1,6 @@
 Name: UIAutomator
 Short Name: uiautomator
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/test/uiautomator/uiautomator/2.4.0-SNAPSHOT/uiautomator-2.4.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/test/uiautomator/uiautomator/2.4.0-SNAPSHOT/uiautomator-2.4.0-20260228.045313-1.aar
 Version: 2.4.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator_shell_android/README.chromium b/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator_shell_android/README.chromium
index b5545532..ff367ece 100644
--- a/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator_shell_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator_shell_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Shell
 Short Name: uiautomator-shell-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/test/uiautomator/uiautomator-shell-android/2.4.0-SNAPSHOT/uiautomator-shell-android-2.4.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/test/uiautomator/uiautomator-shell-android/2.4.0-SNAPSHOT/uiautomator-shell-android-2.4.0-20260228.045313-1.aar
 Version: 2.4.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_tracing_tracing_android/README.chromium b/third_party/androidx/committed/libs/androidx_tracing_tracing_android/README.chromium
index 5366c8b2..2e4d49f4 100644
--- a/third_party/androidx/committed/libs/androidx_tracing_tracing_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_tracing_tracing_android/README.chromium
@@ -1,6 +1,6 @@
 Name: Tracing
 Short Name: tracing-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/tracing/tracing-android/2.0.0-SNAPSHOT/tracing-android-2.0.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/tracing/tracing-android/2.0.0-SNAPSHOT/tracing-android-2.0.0-20260228.045313-1.aar
 Version: 2.0.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_tracing_tracing_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_tracing_tracing_ktx/README.chromium
index 9f1c1772..13228ea9 100644
--- a/third_party/androidx/committed/libs/androidx_tracing_tracing_ktx/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_tracing_tracing_ktx/README.chromium
@@ -1,6 +1,6 @@
 Name: Tracing Kotlin Extensions
 Short Name: tracing-ktx
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/tracing/tracing-ktx/2.0.0-SNAPSHOT/tracing-ktx-2.0.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/tracing/tracing-ktx/2.0.0-SNAPSHOT/tracing-ktx-2.0.0-20260228.045313-1.aar
 Version: 2.0.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_viewpager2_viewpager2/README.chromium b/third_party/androidx/committed/libs/androidx_viewpager2_viewpager2/README.chromium
index 080b835..9a07c5d 100644
--- a/third_party/androidx/committed/libs/androidx_viewpager2_viewpager2/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_viewpager2_viewpager2/README.chromium
@@ -1,6 +1,6 @@
 Name: ViewPager2
 Short Name: viewpager2
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/viewpager2/viewpager2/1.2.0-SNAPSHOT/viewpager2-1.2.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/viewpager2/viewpager2/1.2.0-SNAPSHOT/viewpager2-1.2.0-20260228.045313-1.aar
 Version: 1.2.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_webkit_webkit/README.chromium b/third_party/androidx/committed/libs/androidx_webkit_webkit/README.chromium
index d9a1d07..d5a2dd06 100644
--- a/third_party/androidx/committed/libs/androidx_webkit_webkit/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_webkit_webkit/README.chromium
@@ -1,6 +1,6 @@
 Name: Webkit
 Short Name: webkit
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/webkit/webkit/1.16.0-SNAPSHOT/webkit-1.16.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/webkit/webkit/1.16.0-SNAPSHOT/webkit-1.16.0-20260228.045313-1.aar
 Version: 1.16.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_window_sidecar_sidecar/README.chromium b/third_party/androidx/committed/libs/androidx_window_sidecar_sidecar/README.chromium
index 439f4d7..63edbb21 100644
--- a/third_party/androidx/committed/libs/androidx_window_sidecar_sidecar/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_window_sidecar_sidecar/README.chromium
@@ -1,6 +1,6 @@
 Name: WindowManager Sidecar
 Short Name: sidecar
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/window/sidecar/sidecar/1.0.0-SNAPSHOT/sidecar-1.0.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/window/sidecar/sidecar/1.0.0-SNAPSHOT/sidecar-1.0.0-20260228.045313-1.aar
 Version: 1.0.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_window_window/README.chromium b/third_party/androidx/committed/libs/androidx_window_window/README.chromium
index 9f35f70c..80f9c3b 100644
--- a/third_party/androidx/committed/libs/androidx_window_window/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_window_window/README.chromium
@@ -1,6 +1,6 @@
 Name: WindowManager
 Short Name: window
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/window/window/1.6.0-SNAPSHOT/window-1.6.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/window/window/1.6.0-SNAPSHOT/window-1.6.0-20260228.045313-1.aar
 Version: 1.6.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_window_window_core_android/README.chromium b/third_party/androidx/committed/libs/androidx_window_window_core_android/README.chromium
index 5fa652ae..a51ab27 100644
--- a/third_party/androidx/committed/libs/androidx_window_window_core_android/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_window_window_core_android/README.chromium
@@ -1,6 +1,6 @@
 Name: WindowManager Core
 Short Name: window-core-android
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/window/window-core-android/1.6.0-SNAPSHOT/window-core-android-1.6.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/window/window-core-android/1.6.0-SNAPSHOT/window-core-android-1.6.0-20260228.045313-1.aar
 Version: 1.6.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_work_work_multiprocess/README.chromium b/third_party/androidx/committed/libs/androidx_work_work_multiprocess/README.chromium
index 8803c47..b8b4913 100644
--- a/third_party/androidx/committed/libs/androidx_work_work_multiprocess/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_work_work_multiprocess/README.chromium
@@ -1,6 +1,6 @@
 Name: WorkManager Multiprocess
 Short Name: work-multiprocess
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/work/work-multiprocess/2.12.0-SNAPSHOT/work-multiprocess-2.12.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/work/work-multiprocess/2.12.0-SNAPSHOT/work-multiprocess-2.12.0-20260228.045313-1.aar
 Version: 2.12.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_work_work_runtime/README.chromium b/third_party/androidx/committed/libs/androidx_work_work_runtime/README.chromium
index c165a64..5ce76a6 100644
--- a/third_party/androidx/committed/libs/androidx_work_work_runtime/README.chromium
+++ b/third_party/androidx/committed/libs/androidx_work_work_runtime/README.chromium
@@ -1,6 +1,6 @@
 Name: WorkManager Runtime
 Short Name: work-runtime
-URL: https://androidx.dev/snapshots/builds/14950004/artifacts/repository/androidx/work/work-runtime/2.12.0-SNAPSHOT/work-runtime-2.12.0-20260227.122158-1.aar
+URL: https://androidx.dev/snapshots/builds/14954281/artifacts/repository/androidx/work/work-runtime/2.12.0-SNAPSHOT/work-runtime-2.12.0-20260228.045313-1.aar
 Version: 2.12.0-SNAPSHOT
 Update Mechanism: Autoroll
 License: Apache-2.0
diff --git a/third_party/angle b/third_party/angle
index a326d25..8807d22 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit a326d25100d1d09038de1d63d1d0676a59b24ffe
+Subproject commit 8807d22fb5c5cb62eb4c8233abfb0d5c6af1e999
diff --git a/third_party/blink/renderer/core/animation/view_timeline.cc b/third_party/blink/renderer/core/animation/view_timeline.cc
index c766211e..b09dc68 100644
--- a/third_party/blink/renderer/core/animation/view_timeline.cc
+++ b/third_party/blink/renderer/core/animation/view_timeline.cc
@@ -400,7 +400,7 @@
     return;
   }
 
-  StickyPositionScrollingConstraints* constraints =
+  StickyPositionScrollingConstraints constraints =
       sticky_container->StickyConstraints();
   if (!constraints) {
     return;
@@ -409,7 +409,7 @@
   const PhysicalAxis axis = orientation == kHorizontalScroll
                                 ? PhysicalAxis::kHorizontal
                                 : PhysicalAxis::kVertical;
-  const auto* axis_data = constraints->AxisData(axis);
+  const auto* axis_data = constraints.AxisData(axis);
   if (!axis_data) {
     return;
   }
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index ae0410b..282839e7 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -12420,23 +12420,20 @@
 //    losing focus (not any ancestors), and then SetFocused(true) is called on
 //    the element gaining focus. Because the ancestor chain is not automatically
 //    notified, this function must walk the ancestors manually.
-void Element::HandleInterestForHoverOrFocus(InterestSource source,
-                                            bool recursive_call) {
+void Element::HandleInterestForHoverOrFocus(InterestSource source) {
   DCHECK(RuntimeEnabledFeatures::HTMLInterestForAttributeEnabled());
   if (!IsInTreeScope() || !GetDocument().IsActive()) {
     return;
   }
-  // We manually "bubble" all calls to this function to all ancestors.
-  if (!recursive_call) {
-    for (auto& node : FlatTreeTraversal::InclusiveAncestorsOf(*this)) {
-      if (Element* element = DynamicTo<Element>(node)) {
-        element->HandleInterestForHoverOrFocus(source,
-                                               /*recursive_call*/ true);
-      }
+  for (Node& node : FlatTreeTraversal::InclusiveAncestorsOf(*this)) {
+    if (Element* element = DynamicTo<Element>(node)) {
+      element->ScheduleInterestChangesIfNeeded(source);
     }
-    return;
   }
+}
 
+void Element::ScheduleInterestChangesIfNeeded(InterestSource source) {
+  DCHECK(RuntimeEnabledFeatures::HTMLInterestForAttributeEnabled());
   InvokerData* invoker_data = GetInvokerData();
   Element* upstream_invoker = SourceInterestInvoker();
   InvokerData* upstream_data =
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 51dbbdb..0f463227 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -2571,8 +2571,8 @@
     kFocus,
     kBlur,
   };
-  void HandleInterestForHoverOrFocus(InterestSource source,
-                                     bool recursive_call = false);
+  void HandleInterestForHoverOrFocus(InterestSource source);
+  void ScheduleInterestChangesIfNeeded(InterestSource source);
 
   // Highlight pseudos inherit all properties from the corresponding highlight
   // in the parent, but virtually all existing content uses universal rules
diff --git a/third_party/blink/renderer/core/dom/element_test.cc b/third_party/blink/renderer/core/dom/element_test.cc
index 4ab103c..36bfabd 100644
--- a/third_party/blink/renderer/core/dom/element_test.cc
+++ b/third_party/blink/renderer/core/dom/element_test.cc
@@ -1056,8 +1056,8 @@
 TEST_F(ElementTest, ParseFocusgroupAttrNoMemoryToken) {
   Document& document = GetDocument();
   SetBodyContent(R"HTML(
-    <div id=a focusgroup="toolbar nomemory"></div>
-    <div id=b focusgroup="listbox inline nomemory"></div>
+    <div id=a focusgroup="toolbar no-memory"></div>
+    <div id=b focusgroup="listbox inline no-memory"></div>
   )HTML");
 
   auto* a = document.getElementById(AtomicString("a"));
@@ -1225,7 +1225,7 @@
       static_cast<FocusgroupFlags>(FocusgroupFlags::kBlock |
                                    FocusgroupFlags::kNoMemory)};
   EXPECT_EQ(
-      "toolbar:(block|nomemory)",
+      "toolbar:(block|no-memory)",
       focusgroup::FocusgroupDataToStringForTesting(toolbar_no_memory_data));
 }
 
diff --git a/third_party/blink/renderer/core/dom/events/event_target.h b/third_party/blink/renderer/core/dom/events/event_target.h
index 2fb4b367..a73bcb1 100644
--- a/third_party/blink/renderer/core/dom/events/event_target.h
+++ b/third_party/blink/renderer/core/dom/events/event_target.h
@@ -246,6 +246,7 @@
   DEFINE_ATTRIBUTE_EVENT_LISTENER(click, kClick)
   DEFINE_ATTRIBUTE_EVENT_LISTENER(close, kClose)
   DEFINE_ATTRIBUTE_EVENT_LISTENER(command, kCommand)
+  DEFINE_ATTRIBUTE_EVENT_LISTENER(complete, kComplete)
   DEFINE_ATTRIBUTE_EVENT_LISTENER(contentvisibilityautostatechange,
                                   kContentvisibilityautostatechange)
   DEFINE_ATTRIBUTE_EVENT_LISTENER(contextmenu, kContextmenu)
diff --git a/third_party/blink/renderer/core/dom/focusgroup_flags.cc b/third_party/blink/renderer/core/dom/focusgroup_flags.cc
index 2748883..c69d8b9 100644
--- a/third_party/blink/renderer/core/dom/focusgroup_flags.cc
+++ b/third_party/blink/renderer/core/dom/focusgroup_flags.cc
@@ -67,7 +67,7 @@
     {"flow", FocusgroupFlags::kRowFlow | FocusgroupFlags::kColFlow},
     {"row-flow", FocusgroupFlags::kRowFlow},
     {"col-flow", FocusgroupFlags::kColFlow},
-    {"nomemory", FocusgroupFlags::kNoMemory},
+    {"no-memory", FocusgroupFlags::kNoMemory},
 };
 
 // Returns true if a flag contains a modifier only meaningful for grid
diff --git a/third_party/blink/renderer/core/dom/focusgroup_flags_test.cc b/third_party/blink/renderer/core/dom/focusgroup_flags_test.cc
index 05c1b0f..5c893f9 100644
--- a/third_party/blink/renderer/core/dom/focusgroup_flags_test.cc
+++ b/third_party/blink/renderer/core/dom/focusgroup_flags_test.cc
@@ -436,24 +436,4 @@
   EXPECT_TRUE(messages[0].contains("nowrap"));
 }
 
-TEST_F(FocusgroupFlagsTest, NomemoryModifierSetsFlag) {
-  ScopedFocusgroupForTest focusgroup_scope(true);
-
-  auto* element = MakeGarbageCollected<HTMLDivElement>(GetDocument());
-  GetDocument().body()->appendChild(element);
-
-  ClearConsoleMessages();
-  FocusgroupData result =
-      ParseFocusgroup(element, AtomicString("toolbar nomemory"));
-
-  EXPECT_EQ(result.behavior, FocusgroupBehavior::kToolbar);
-  EXPECT_TRUE(result.flags & FocusgroupFlags::kNoMemory);
-  // Should also have default axes.
-  EXPECT_TRUE(result.flags & FocusgroupFlags::kInline);
-  EXPECT_TRUE(result.flags & FocusgroupFlags::kBlock);
-
-  auto messages = CopyConsoleMessages();
-  EXPECT_EQ(messages.size(), 0u);
-}
-
 }  // namespace blink::focusgroup
diff --git a/third_party/blink/renderer/core/dom/global_event_handlers.idl b/third_party/blink/renderer/core/dom/global_event_handlers.idl
index 4836481..b5702c5 100644
--- a/third_party/blink/renderer/core/dom/global_event_handlers.idl
+++ b/third_party/blink/renderer/core/dom/global_event_handlers.idl
@@ -41,6 +41,7 @@
   attribute EventHandler onclick;
   attribute EventHandler onclose;
   attribute EventHandler oncommand;
+  [RuntimeEnabled=LoginElement] attribute EventHandler oncomplete;
   attribute EventHandler oncontentvisibilityautostatechange;
   attribute EventHandler oncontextlost;
   attribute EventHandler oncontextmenu;
diff --git a/third_party/blink/renderer/core/frame/remote_frame_view.cc b/third_party/blink/renderer/core/frame/remote_frame_view.cc
index 33931bcc..54ddd4a 100644
--- a/third_party/blink/renderer/core/frame/remote_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/remote_frame_view.cc
@@ -384,7 +384,7 @@
     context.Restore();
   }
 
-  if (GetFrame().GetCcLayer()) {
+  if (GetFrame().GetCcLayer() && !paint_info.IsPrivacyPreserving()) {
     RecordForeignLayer(
         context, owner_layout_object, DisplayItem::kForeignLayerRemoteFrame,
         GetFrame().GetCcLayer(), FrameRect().origin() + paint_offset);
diff --git a/third_party/blink/renderer/core/html/DEPS b/third_party/blink/renderer/core/html/DEPS
index 98e5fa90..a641970e 100644
--- a/third_party/blink/renderer/core/html/DEPS
+++ b/third_party/blink/renderer/core/html/DEPS
@@ -17,6 +17,16 @@
     "html_anchor_element.cc" : [
         "+base/command_line.h"
     ],
+    "html_login_element.cc" : [
+        "+services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-blink.h",
+    ],
+    "html_login_element_test.cc" : [
+        "+base/run_loop.h",
+        "+mojo/public/cpp/bindings/receiver.h",
+        "+services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-blink.h",
+        "+services/network/public/cpp/permissions_policy/permissions_policy.h",
+        "+services/network/public/cpp/permissions_policy/permissions_policy_declaration.h",
+    ],
     "html_meta_element.h": [
         "+services/network/public/cpp/client_hints.h",
     ],
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc
index c5dbd8b..4413d8a 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc
@@ -194,52 +194,9 @@
   return true;
 }
 
-std::optional<cc::PaintRecord> CanvasRenderingContext::GetElementPaintRecord(
-    Element* element,
-    std::optional<CullRect> cull_rect,
-    const String& func_name,
-    ExceptionState& exception_state) {
-  if (!IsDrawElementImageEligible(element, func_name, exception_state)) {
-    return std::nullopt;
-  }
-
-  PaintRecordBuilder builder;
-  LayoutBox* layout_box = element->GetLayoutBox();
-  // All drawn elements should have their own stacking contexts.
-  CHECK(layout_box->HasLayer());
-  CHECK(layout_box->IsStacked());
-  PaintLayer* layer = layout_box->EnclosingLayer();
-
-  if (!cull_rect) {
-    auto box_rect =
-        gfx::Rect(ToCeiledSize(layer->GetLayoutBox()->StitchedSize()));
-    cull_rect.emplace(box_rect);
-  }
-
-  OverriddenCullRectScope cull_rect_scope(*layer, *cull_rect,
-                                          /*disable_expansion*/ true);
-
-  PaintLayerPainter paint_layer_painter = PaintLayerPainter(*layer);
-  paint_layer_painter.Paint(
-      builder.Context(),
-      PaintFlag::kPrivacyPreserving | PaintFlag::kOmitCompositingInfo);
-
-  // Use the drawn element's local property tree state to start drawing, but
-  // then modify this to include effects and clips between the drawn element
-  // and the canvas element. This will exclude transforms above the local
-  // border box state (e.g., css transform is ignored), but will include effects
-  // (e.g., css filter is not ignored).
-  PropertyTreeState property_tree_state = layer->GetLayoutBox()
-                                              ->FirstFragment()
-                                              .LocalBorderBoxProperties()
-                                              .Unalias();
-  HTMLCanvasElement* canvas_element = static_cast<HTMLCanvasElement*>(Host());
-  const auto& canvas_fragment = canvas_element->GetLayoutBox()->FirstFragment();
-  property_tree_state.SetEffect(canvas_fragment.ContentsEffect().Unalias());
-  property_tree_state.SetClip(canvas_fragment.ContentsClip().Unalias());
-
-  cc::PaintRecord paint_record = builder.EndRecording(property_tree_state);
-  return paint_record;
+std::optional<CanvasChildPaintRecord>
+CanvasRenderingContext::GetChildPaintRecord(Element* element) {
+  return Host()->GetCanvasChildPaintRecord(element->GetDomNodeId());
 }
 
 scoped_refptr<StaticBitmapImage> CanvasRenderingContext::GetElementImage(
@@ -255,25 +212,25 @@
   element->GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
       DocumentUpdateReason::kCanvasDrawElementImage);
 
-  // Element size in physical coordinates.
-  gfx::SizeF box_size;
-  if (element->GetLayoutBox()) {
-    box_size = gfx::SizeF(element->GetLayoutBox()->StitchedSize());
-  }
-  gfx::RectF src_rect(box_size);
-  std::optional<CullRect> cull_rect;
-  if (sx && sy && swidth && sheight) {
-    float dpr = element->ComputedStyleRef().EffectiveZoom();
-    src_rect = gfx::RectF(*sx * dpr, *sy * dpr, *swidth * dpr, *sheight * dpr);
-    cull_rect.emplace(gfx::ToEnclosingRect(src_rect));
+  if (!IsDrawElementImageEligible(element, func_name, exception_state)) {
+    return nullptr;
   }
 
-  std::optional<cc::PaintRecord> paint_record =
-      GetElementPaintRecord(element, cull_rect, func_name, exception_state);
-  if (!paint_record) {
+  std::optional<CanvasChildPaintRecord> child_paint_record =
+      GetChildPaintRecord(element);
+  if (!child_paint_record) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "No cached paint record for element.");
     return nullptr;
   }
 
+  // Element size in physical coordinates.
+  gfx::RectF src_rect(child_paint_record->box_size);
+  if (sx && sy && swidth && sheight) {
+    float dpr = child_paint_record->scale;
+    src_rect = gfx::RectF(*sx * dpr, *sy * dpr, *swidth * dpr, *sheight * dpr);
+  }
+
   HTMLCanvasElement* canvas_element = static_cast<HTMLCanvasElement*>(Host());
 
   // The default destination size for GetElementImage is the source content
@@ -303,7 +260,7 @@
   SkiaPaintCanvas skia_paint_canvas(surface->getCanvas());
   skia_paint_canvas.scale(canvas_scale.x(), canvas_scale.y());
   skia_paint_canvas.translate(-src_rect.x(), -src_rect.y());
-  skia_paint_canvas.drawPicture(*paint_record);
+  skia_paint_canvas.drawPicture(child_paint_record->record);
   return UnacceleratedStaticBitmapImage::Create(surface->makeImageSnapshot());
 }
 
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 b100d82..af36d47 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,7 +70,6 @@
 namespace blink {
 
 class ComputedStyle;
-class CullRect;
 class Document;
 class Element;
 class ExceptionState;
@@ -344,11 +343,7 @@
                                   const String& func_name,
                                   ExceptionState& exception_state);
 
-  std::optional<cc::PaintRecord> GetElementPaintRecord(
-      Element*,
-      std::optional<CullRect> cull_rect,
-      const String& func_name,
-      ExceptionState&);
+  std::optional<CanvasChildPaintRecord> GetChildPaintRecord(Element* element);
 
   std::optional<cc::PaintRecord> empty_recording_;
 
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 605614fa..9d49f8d 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
@@ -16,6 +16,7 @@
 #include "third_party/blink/renderer/core/html/canvas/ukm_parameters.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/v8_external_memory_accounter.h"
+#include "third_party/blink/renderer/platform/graphics/canvas_child_paint_record.h"
 #include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/text/text_direction.h"
@@ -151,6 +152,11 @@
 
   virtual void DiscardResources() = 0;
 
+  virtual std::optional<CanvasChildPaintRecord> GetCanvasChildPaintRecord(
+      DOMNodeId child_id) const {
+    return std::nullopt;
+  }
+
  protected:
   ~CanvasRenderingContextHost() override;
 
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
index 6436e5c..f5293b64 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
@@ -109,6 +109,7 @@
 #include "third_party/blink/renderer/platform/graphics/canvas_resource.h"
 #include "third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h"
 #include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
+#include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/shared_context_rate_limiter.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
@@ -1682,6 +1683,16 @@
   dirty_rect_ = gfx::Rect();
 }
 
+std::optional<CanvasChildPaintRecord>
+HTMLCanvasElement::GetCanvasChildPaintRecord(DOMNodeId child_id) const {
+  if (auto* view = GetDocument().View()) {
+    if (auto* pac = view->GetPaintArtifactCompositor()) {
+      return pac->GetCanvasChildPaintRecord(child_id);
+    }
+  }
+  return std::nullopt;
+}
+
 void HTMLCanvasElement::UpdateSuspendOffscreenCanvasAnimation() {
   if (!GetPage()) {
     return;
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.h b/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
index bfd65c3..939f18b 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
@@ -189,6 +189,9 @@
 
   void DiscardResources() override;
 
+  std::optional<CanvasChildPaintRecord> GetCanvasChildPaintRecord(
+      DOMNodeId child_id) const override;
+
   TextDirection GetTextDirection(const ComputedStyle*) override;
   const LayoutLocale* GetLocale() const override;
 
diff --git a/third_party/blink/renderer/core/html/forms/html_form_element.h b/third_party/blink/renderer/core/html/forms/html_form_element.h
index 25725b7..1a26df0 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_form_element.h
@@ -260,6 +260,7 @@
       CHECK(!tool_name.IsNull() && !tool_description.IsNull());
     }
     String ComputeInputSchema() override;
+    Element* FormElement() const override { return form_; }
     void ExecuteTool(
         String input_arguments,
         base::OnceCallback<void(McpToolCallbackResult)> done_callback) override;
diff --git a/third_party/blink/renderer/core/html/html_attribute_names.json5 b/third_party/blink/renderer/core/html/html_attribute_names.json5
index 6614107..012bbe4 100644
--- a/third_party/blink/renderer/core/html/html_attribute_names.json5
+++ b/third_party/blink/renderer/core/html/html_attribute_names.json5
@@ -52,6 +52,7 @@
     "cite",
     "class",
     "classid",
+    "clientid",
     "clear",
     "closedby",
     "code",
@@ -59,6 +60,7 @@
     "codetype",
     "color",
     "cols",
+    "configurl",
     "colspan",
     "command",
     "commandfor",
@@ -85,6 +87,7 @@
     "dir",
     "direction",
     "dirname",
+    "domainhint",
     "disabled",
     "disablepictureinpicture",
     "disableremoteplayback",
@@ -98,6 +101,7 @@
     "exportparts",
     "face",
     "fetchpriority",
+    "fields",
     "filter",
     "focusgroup",
     "focusgroupstart",
@@ -145,6 +149,7 @@
     "link",
     "list",
     "loading",
+    "loginhint",
     "longdesc",
     "loop",
     "low",
@@ -311,6 +316,7 @@
     "overscrollcontainer",
     "parseparts",
     "part",
+    "params",
     "pattern",
     "placeholder",
     "playsinline",
diff --git a/third_party/blink/renderer/core/html/html_credential_element.cc b/third_party/blink/renderer/core/html/html_credential_element.cc
index b8537fe1..ed0242b 100644
--- a/third_party/blink/renderer/core/html/html_credential_element.cc
+++ b/third_party/blink/renderer/core/html/html_credential_element.cc
@@ -4,11 +4,111 @@
 
 #include "third_party/blink/renderer/core/html/html_credential_element.h"
 
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
 #include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/inspector/console_message.h"
+#include "third_party/blink/renderer/platform/json/json_parser.h"
+#include "third_party/blink/renderer/platform/json/json_values.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_concatenate.h"
 
 namespace blink {
 
 HTMLCredentialElement::HTMLCredentialElement(Document& document)
     : HTMLElement(html_names::kCredentialTag, document) {}
 
+bool HTMLCredentialElement::IsURLAttribute(const Attribute& attribute) const {
+  return attribute.GetName() == html_names::kConfigurlAttr ||
+         HTMLElement::IsURLAttribute(attribute);
+}
+
+void HTMLCredentialElement::ParseAttribute(
+    const AttributeModificationParams& params) {
+  if (params.name == html_names::kParamsAttr && !params.new_value.IsNull()) {
+    JSONParseError parse_error;
+    if (!ParseJSON(params.new_value, &parse_error)) {
+      GetExecutionContext()->AddConsoleMessage(
+          MakeGarbageCollected<ConsoleMessage>(
+              mojom::blink::ConsoleMessageSource::kOther,
+              mojom::blink::ConsoleMessageLevel::kError,
+              StrCat({"credential params attribute was invalid JSON: ",
+                      parse_error.message, " (line ",
+                      String::Number(parse_error.line), ", col ",
+                      String::Number(parse_error.column), ")"})));
+    }
+  } else if (params.name == html_names::kConfigurlAttr &&
+             !params.new_value.IsNull()) {
+    KURL config_url = GetDocument().CompleteURL(params.new_value);
+    if (params.new_value.empty() || !config_url.IsValid()) {
+      GetExecutionContext()->AddConsoleMessage(
+          MakeGarbageCollected<ConsoleMessage>(
+              mojom::blink::ConsoleMessageSource::kJavaScript,
+              mojom::blink::ConsoleMessageLevel::kError,
+              StrCat({"credential configurl attribute was an invalid URL: ",
+                      params.new_value})));
+    }
+  }
+  HTMLElement::ParseAttribute(params);
+}
+
+mojom::blink::IdentityProviderRequestOptionsPtr
+HTMLCredentialElement::GetFederatedRequestOptions() const {
+  String type = FastGetAttribute(html_names::kTypeAttr);
+  if (type != "federated") {
+    return nullptr;
+  }
+
+  KURL config_url = GetNonEmptyURLAttribute(html_names::kConfigurlAttr);
+  if (config_url.IsEmpty() || !config_url.IsValid()) {
+    return nullptr;
+  }
+
+  if (!GetExecutionContext()->GetContentSecurityPolicy()->AllowConnectToSource(
+          config_url, config_url, ResourceRequest::RedirectStatus::kNoRedirect,
+          ReportingDisposition::kSuppressReporting,
+          ContentSecurityPolicy::CheckHeaderType::kCheckAll)) {
+    GetExecutionContext()->AddConsoleMessage(
+        MakeGarbageCollected<ConsoleMessage>(
+            mojom::blink::ConsoleMessageSource::kJavaScript,
+            mojom::blink::ConsoleMessageLevel::kError,
+            StrCat({"Refused to connect to '", config_url.ElidedString(),
+                    "' because it violates the document's Content Security "
+                    "Policy."})));
+    return nullptr;
+  }
+
+  auto options = mojom::blink::IdentityProviderRequestOptions::New();
+  options->config = mojom::blink::IdentityProviderConfig::New();
+  options->config->config_url = std::move(config_url);
+  options->config->client_id = FastGetAttribute(html_names::kClientidAttr);
+  if (options->config->client_id.IsNull()) {
+    options->config->client_id = g_empty_string;
+  }
+
+  // Initialize non-nullable fields to satisfy mojom validation.
+  options->nonce = g_empty_string;
+  options->login_hint = FastGetAttribute(html_names::kLoginhintAttr);
+  options->domain_hint = FastGetAttribute(html_names::kDomainhintAttr);
+  if (options->login_hint.IsNull()) {
+    options->login_hint = g_empty_string;
+  }
+  if (options->domain_hint.IsNull()) {
+    options->domain_hint = g_empty_string;
+  }
+
+  String fields_attr = FastGetAttribute(html_names::kFieldsAttr);
+  if (!fields_attr.IsNull()) {
+    options->fields = fields_attr.Split(',');
+  }
+
+  String params_attr = FastGetAttribute(html_names::kParamsAttr);
+  if (!params_attr.IsNull() && ParseJSON(params_attr, nullptr)) {
+    options->params_json = params_attr;
+  }
+
+  return options;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/html_credential_element.h b/third_party/blink/renderer/core/html/html_credential_element.h
index 46fd1ad..2b66ffd3 100644
--- a/third_party/blink/renderer/core/html/html_credential_element.h
+++ b/third_party/blink/renderer/core/html/html_credential_element.h
@@ -5,16 +5,31 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_HTML_CREDENTIAL_ELEMENT_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_HTML_CREDENTIAL_ELEMENT_H_
 
+#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom-blink.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
 
 namespace blink {
 
+// <credential> element is used to provide configuration for Federated
+// Credential Management (FedCM) requests when used as a child of a <login>
+// element.
+// See https://github.com/fedidcg/login-element for the explainer.
 class CORE_EXPORT HTMLCredentialElement : public HTMLElement {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
   explicit HTMLCredentialElement(Document&);
+
+  // Returns the FederatedAuthRequest options derived from the element's
+  // attributes (e.g. configURL, clientID, etc.). Returns null if the element is
+  // not a valid credential configuration.
+  mojom::blink::IdentityProviderRequestOptionsPtr GetFederatedRequestOptions()
+      const;
+
+ private:
+  void ParseAttribute(const AttributeModificationParams&) override;
+  bool IsURLAttribute(const Attribute&) const override;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/html_credential_element.idl b/third_party/blink/renderer/core/html/html_credential_element.idl
index a1e2259..55ee2209 100644
--- a/third_party/blink/renderer/core/html/html_credential_element.idl
+++ b/third_party/blink/renderer/core/html/html_credential_element.idl
@@ -7,4 +7,11 @@
     Exposed=Window,
     RuntimeEnabled=LoginElement
 ] interface HTMLCredentialElement : HTMLElement {
+    [CEReactions, Reflect] attribute DOMString type;
+    [CEReactions, Reflect] attribute DOMString clientId;
+    [CEReactions, Reflect, URL] attribute USVString configURL;
+    [CEReactions, Reflect] attribute DOMString loginHint;
+    [CEReactions, Reflect] attribute DOMString domainHint;
+    [CEReactions, Reflect] attribute DOMString fields;
+    [CEReactions, Reflect] attribute DOMString params;
 };
diff --git a/third_party/blink/renderer/core/html/html_credential_element_test.cc b/third_party/blink/renderer/core/html/html_credential_element_test.cc
index 6a0e026..7b6a0d8 100644
--- a/third_party/blink/renderer/core/html/html_credential_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_credential_element_test.cc
@@ -4,11 +4,21 @@
 
 #include "third_party/blink/renderer/core/html/html_credential_element.h"
 
+#include "services/network/public/cpp/permissions_policy/permissions_policy.h"
+#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-blink.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/execution_context/security_context.h"
+#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
 #include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/inspector/console_message.h"
+#include "third_party/blink/renderer/core/inspector/console_message_storage.h"
+#include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/network/http_parsers.h"
+#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 
 namespace blink {
 
@@ -20,4 +30,110 @@
   EXPECT_EQ(element->localName(), "credential");
 }
 
+TEST_F(HTMLCredentialElementTest, GetFederatedRequestOptions_Success) {
+  NavigateTo(KURL("https://example.com"));
+  auto* element = MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  element->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  element->setAttribute(html_names::kConfigurlAttr,
+                        AtomicString("https://idp.com/config.json"));
+  element->setAttribute(html_names::kClientidAttr, AtomicString("client123"));
+
+  auto options = element->GetFederatedRequestOptions();
+  ASSERT_TRUE(options);
+  EXPECT_EQ(options->config->config_url, "https://idp.com/config.json");
+  EXPECT_EQ(options->config->client_id, "client123");
+}
+
+TEST_F(HTMLCredentialElementTest, GetFederatedRequestOptions_BlockedByCSP) {
+  NavigateTo(KURL("https://example.com"));
+  ExecutionContext* context = GetDocument().GetExecutionContext();
+
+  // Set up CSP to block the IDP.
+  context->GetContentSecurityPolicy()->AddPolicies(ParseContentSecurityPolicies(
+      "connect-src https://allowed.com",
+      network::mojom::blink::ContentSecurityPolicyType::kEnforce,
+      network::mojom::blink::ContentSecurityPolicySource::kHTTP,
+      *(context->GetSecurityOrigin())));
+
+  ConsoleMessageStorage& storage = GetPage().GetConsoleMessageStorage();
+  wtf_size_t initial_size = storage.size();
+
+  auto* element = MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  GetDocument().body()->AppendChild(element);
+
+  element->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  element->setAttribute(html_names::kConfigurlAttr,
+                        AtomicString("https://blocked.com/config.json"));
+  element->setAttribute(html_names::kClientidAttr, AtomicString("client123"));
+
+  // Should return nullptr because of CSP violation.
+  EXPECT_FALSE(element->GetFederatedRequestOptions());
+
+  // Check that a console message was emitted immediately.
+  ASSERT_EQ(storage.size(), initial_size + 1);
+  ASSERT_GT(storage.size(), 0u);
+  EXPECT_TRUE(storage.at(storage.size() - 1)
+                  ->Message()
+                  .contains("Refused to connect to"));
+
+  // Ensure no additional message is emitted.
+  EXPECT_EQ(storage.size(), initial_size + 1);
+}
+
+TEST_F(HTMLCredentialElementTest, ParamsAttributeValidation) {
+  NavigateTo(KURL("https://example.com"));
+  auto* element = MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  element->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  element->setAttribute(html_names::kConfigurlAttr,
+                        AtomicString("https://idp.com/config.json"));
+
+  ConsoleMessageStorage& storage = GetPage().GetConsoleMessageStorage();
+  wtf_size_t initial_size = storage.size();
+
+  // Set invalid JSON in params.
+  element->setAttribute(html_names::kParamsAttr,
+                        AtomicString("{invalid: json}"));
+
+  // Check that a console message was emitted.
+  EXPECT_EQ(storage.size(), initial_size + 1);
+  EXPECT_TRUE(
+      storage.at(storage.size() - 1)->Message().contains("invalid JSON"));
+
+  // Call GetFederatedRequestOptions and ensure it returns options but with
+  // params_json unset (null).
+  auto options = element->GetFederatedRequestOptions();
+  ASSERT_TRUE(options);
+  EXPECT_TRUE(options->params_json.IsNull());
+  EXPECT_EQ(storage.size(), initial_size + 1);
+
+  // Set valid JSON.
+  element->setAttribute(html_names::kParamsAttr,
+                        AtomicString("{\"valid\": \"json\"}"));
+  options = element->GetFederatedRequestOptions();
+  ASSERT_TRUE(options);
+  EXPECT_EQ(options->params_json, "{\"valid\": \"json\"}");
+  // No new error message should be added.
+  EXPECT_EQ(storage.size(), initial_size + 1);
+}
+
+TEST_F(HTMLCredentialElementTest, ConfigUrlValidation) {
+  NavigateTo(KURL("https://example.com"));
+  auto* element = MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  element->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+
+  ConsoleMessageStorage& storage = GetPage().GetConsoleMessageStorage();
+  wtf_size_t initial_size = storage.size();
+
+  // Set invalid URL in configurl.
+  element->setAttribute(html_names::kConfigurlAttr, AtomicString("https://["));
+
+  // Check that a console message was emitted.
+  EXPECT_EQ(storage.size(), initial_size + 1);
+  EXPECT_TRUE(
+      storage.at(storage.size() - 1)->Message().contains("invalid URL"));
+
+  // Should return nullptr because of invalid URL.
+  EXPECT_FALSE(element->GetFederatedRequestOptions());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/html_login_element.cc b/third_party/blink/renderer/core/html/html_login_element.cc
index 3f61b9a..a355aa2 100644
--- a/third_party/blink/renderer/core/html/html_login_element.cc
+++ b/third_party/blink/renderer/core/html/html_login_element.cc
@@ -4,11 +4,211 @@
 
 #include "third_party/blink/renderer/core/html/html_login_element.h"
 
+#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-blink.h"
+#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom-blink.h"
+#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
+#include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/public/platform/web_v8_value_converter.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/element_traversal.h"
+#include "third_party/blink/renderer/core/dom/events/event.h"
+#include "third_party/blink/renderer/core/dom/node_traversal.h"
+#include "third_party/blink/renderer/core/event_type_names.h"
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/core/events/mouse_event.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/frame/web_feature.h"
+#include "third_party/blink/renderer/core/html/html_credential_element.h"
 #include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/inspector/console_message.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
 
 namespace blink {
 
+namespace {
+
+bool IsEnterKeyKeydownEvent(Event& event) {
+  auto* keyboard_event = DynamicTo<KeyboardEvent>(event);
+  if (!keyboard_event) {
+    return false;
+  }
+  return keyboard_event->key() == "Enter";
+}
+
+bool IsLoginClick(Event& event) {
+  auto* mouse_event = DynamicTo<MouseEvent>(event);
+  if (!mouse_event || event.type() != event_type_names::kClick) {
+    // TODO(crbug.com/477699742): should we handle touch and pointer events too?
+    return false;
+  }
+  return mouse_event->button() == 0;
+}
+
+}  // namespace
+
 HTMLLoginElement::HTMLLoginElement(Document& document)
-    : HTMLElement(html_names::kLoginTag, document) {}
+    : HTMLElement(html_names::kLoginTag, document),
+      federated_auth_request_(document.GetExecutionContext()) {}
+
+ScriptValue HTMLLoginElement::credential(ScriptState* script_state) const {
+  if (!credential_) {
+    return ScriptValue();
+  }
+
+  // TODO(crbug.com/477699742): consider storing a v8::Local<v8::Value> as
+  // member rather than a base::Value so that we don't have to recompute this
+  // every time on demand.
+  std::unique_ptr<WebV8ValueConverter> converter =
+      Platform::Current()->CreateWebV8ValueConverter();
+  v8::Local<v8::Value> v8_value =
+      converter->ToV8Value(*credential_, script_state->GetContext());
+  return ScriptValue(script_state->GetIsolate(), v8_value);
+}
+
+void HTMLLoginElement::NotifyCredentialReceived(base::Value token) {
+  DCHECK(isConnected());
+  credential_ = std::move(token);
+  DispatchEvent(*Event::Create(event_type_names::kComplete));
+}
+
+Vector<mojom::blink::IdentityProviderRequestOptionsPtr>
+HTMLLoginElement::GetFederatedRequestOptions() const {
+  Vector<mojom::blink::IdentityProviderRequestOptionsPtr> options_list;
+  for (HTMLCredentialElement& credential :
+       Traversal<HTMLCredentialElement>::ChildrenOf(*this)) {
+    if (auto options = credential.GetFederatedRequestOptions()) {
+      options_list.push_back(std::move(options));
+    }
+  }
+  return options_list;
+}
+
+FocusableState HTMLLoginElement::IsFocusableState(
+    UpdateBehavior update_behavior) const {
+  // TODO(crbug.com/477699742): if you tab through the document, will the login
+  // element be focused even if empty?
+  if (!GetFederatedRequestOptions().empty()) {
+    return FocusableState::kFocusable;
+  }
+  return HTMLElement::IsFocusableState(update_behavior);
+}
+
+bool HTMLLoginElement::ShouldHaveFocusAppearance() const {
+  return (IsFocused() && !GetFederatedRequestOptions().empty()) ||
+         HTMLElement::ShouldHaveFocusAppearance();
+}
+
+bool HTMLLoginElement::WillRespondToMouseClickEvents() {
+  return !GetFederatedRequestOptions().empty();
+}
+
+bool HTMLLoginElement::IsInteractiveContent() const {
+  return true;
+}
+
+Node::InsertionNotificationRequest HTMLLoginElement::InsertedInto(
+    ContainerNode& insertion_point) {
+  Node::InsertionNotificationRequest result =
+      HTMLElement::InsertedInto(insertion_point);
+  if (!insertion_point.isConnected()) {
+    return result;
+  }
+
+  if (!GetExecutionContext()->IsSecureContext()) {
+    GetExecutionContext()->AddConsoleMessage(
+        MakeGarbageCollected<ConsoleMessage>(
+            mojom::blink::ConsoleMessageSource::kJavaScript,
+            mojom::blink::ConsoleMessageLevel::kError,
+            "The <login> element can only be used in a secure context."));
+  }
+
+  return result;
+}
+
+void HTMLLoginElement::DefaultEventHandler(Event& event) {
+  if (!isConnected()) {
+    HTMLElement::DefaultEventHandler(event);
+    return;
+  }
+
+  bool is_click = IsLoginClick(event);
+  bool is_enter = IsFocused() && IsEnterKeyKeydownEvent(event);
+
+  if (!is_click && !is_enter) {
+    HTMLElement::DefaultEventHandler(event);
+    return;
+  }
+
+  if (!GetExecutionContext()->IsSecureContext()) {
+    HTMLElement::DefaultEventHandler(event);
+    return;
+  }
+
+  if (!GetExecutionContext()->IsFeatureEnabled(
+          network::mojom::PermissionsPolicyFeature::kIdentityCredentialsGet)) {
+    GetExecutionContext()->AddConsoleMessage(MakeGarbageCollected<
+                                             ConsoleMessage>(
+        mojom::blink::ConsoleMessageSource::kJavaScript,
+        mojom::blink::ConsoleMessageLevel::kError,
+        "The 'identity-credentials-get' permissions policy is not enabled."));
+    HTMLElement::DefaultEventHandler(event);
+    return;
+  }
+
+  Vector<mojom::blink::IdentityProviderGetParametersPtr> idp_get_params;
+
+  for (HTMLCredentialElement& credential :
+       Traversal<HTMLCredentialElement>::ChildrenOf(*this)) {
+    auto options = credential.GetFederatedRequestOptions();
+    if (!options) {
+      continue;
+    }
+
+    auto get_params = mojom::blink::IdentityProviderGetParameters::New();
+    get_params->providers.push_back(std::move(options));
+    get_params->mode = mojom::blink::RpMode::kActive;
+    idp_get_params.push_back(std::move(get_params));
+  }
+
+  if (idp_get_params.empty()) {
+    HTMLElement::DefaultEventHandler(event);
+    return;
+  }
+
+  if (!federated_auth_request_.is_bound()) {
+    GetExecutionContext()->GetBrowserInterfaceBroker().GetInterface(
+        federated_auth_request_.BindNewPipeAndPassReceiver(
+            GetExecutionContext()->GetTaskRunner(TaskType::kInternalDefault)));
+  }
+
+  federated_auth_request_->RequestToken(
+      std::move(idp_get_params),
+      mojom::blink::CredentialMediationRequirement::kRequired,
+      blink::BindOnce(&HTMLLoginElement::OnRequestTokenResponse,
+                      WrapWeakPersistent(this)));
+  event.SetDefaultHandled();
+
+  HTMLElement::DefaultEventHandler(event);
+}
+
+void HTMLLoginElement::OnRequestTokenResponse(
+    mojom::blink::RequestTokenStatus status,
+    const std::optional<KURL>& selected_identity_provider_config_url,
+    std::optional<base::Value> token,
+    mojom::blink::TokenErrorPtr error,
+    bool is_auto_selected) {
+  if (status == mojom::blink::RequestTokenStatus::kSuccess && token) {
+    NotifyCredentialReceived(std::move(*token));
+  }
+}
+
+void HTMLLoginElement::Trace(Visitor* visitor) const {
+  visitor->Trace(federated_auth_request_);
+  HTMLElement::Trace(visitor);
+}
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/html_login_element.h b/third_party/blink/renderer/core/html/html_login_element.h
index e9b8798..bf02f579 100644
--- a/third_party/blink/renderer/core/html/html_login_element.h
+++ b/third_party/blink/renderer/core/html/html_login_element.h
@@ -5,16 +5,57 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_HTML_LOGIN_ELEMENT_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_HTML_LOGIN_ELEMENT_H_
 
+#include "base/values.h"
+#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom-blink.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 
 namespace blink {
 
+// <login> element is used to trigger Federated Credential Management (FedCM)
+// requests when clicked. It uses its <credential> children to determine the
+// identity providers to use for the request.
+// See https://github.com/fedidcg/login-element for the explainer.
 class CORE_EXPORT HTMLLoginElement : public HTMLElement {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
   explicit HTMLLoginElement(Document&);
+
+  ScriptValue credential(ScriptState*) const;
+
+  Vector<mojom::blink::IdentityProviderRequestOptionsPtr>
+  GetFederatedRequestOptions() const;
+
+  DEFINE_ATTRIBUTE_EVENT_LISTENER(complete, kComplete)
+
+  void Trace(Visitor*) const override;
+
+ private:
+  FocusableState IsFocusableState(UpdateBehavior) const override;
+  bool ShouldHaveFocusAppearance() const override;
+
+  bool WillRespondToMouseClickEvents() override;
+  bool IsInteractiveContent() const override;
+
+  void DefaultEventHandler(Event&) override;
+
+  InsertionNotificationRequest InsertedInto(ContainerNode&) override;
+
+  void NotifyCredentialReceived(base::Value token);
+
+  void OnRequestTokenResponse(
+      mojom::blink::RequestTokenStatus status,
+      const std::optional<KURL>& selected_identity_provider_config_url,
+      std::optional<base::Value> token,
+      mojom::blink::TokenErrorPtr error,
+      bool is_auto_selected);
+
+  std::optional<base::Value> credential_;
+
+  HeapMojoRemote<mojom::blink::FederatedAuthRequest> federated_auth_request_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/html_login_element.idl b/third_party/blink/renderer/core/html/html_login_element.idl
index e1f9cfc..4537542 100644
--- a/third_party/blink/renderer/core/html/html_login_element.idl
+++ b/third_party/blink/renderer/core/html/html_login_element.idl
@@ -6,4 +6,5 @@
     Exposed=Window,
     RuntimeEnabled=LoginElement
 ] interface HTMLLoginElement : HTMLElement {
+    [CallWith=ScriptState] readonly attribute any credential;
 };
diff --git a/third_party/blink/renderer/core/html/html_login_element_test.cc b/third_party/blink/renderer/core/html/html_login_element_test.cc
index c586d51e..ac5520d8d 100644
--- a/third_party/blink/renderer/core/html/html_login_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_login_element_test.cc
@@ -4,20 +4,478 @@
 
 #include "third_party/blink/renderer/core/html/html_login_element.h"
 
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "services/network/public/cpp/permissions_policy/permissions_policy.h"
+#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-blink.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom-blink.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_keyboard_event_init.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_mouse_event_init.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_pointer_event_init.h"
 #include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
+#include "third_party/blink/renderer/core/event_type_names.h"
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/core/events/mouse_event.h"
+#include "third_party/blink/renderer/core/events/pointer_event.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/execution_context/security_context.h"
+#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/html/html_credential_element.h"
 #include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/inspector/console_message.h"
+#include "third_party/blink/renderer/core/inspector/console_message_storage.h"
+#include "third_party/blink/renderer/core/page/focus_controller.h"
+#include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/network/http_parsers.h"
+#include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
+#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 
 namespace blink {
 
-class HTMLLoginElementTest : public PageTestBase {};
+class QuitListener : public NativeEventListener {
+ public:
+  explicit QuitListener(base::OnceClosure quit_closure)
+      : quit_closure_(std::move(quit_closure)) {}
 
-TEST_F(HTMLLoginElementTest, TagName) {
+  void Invoke(ExecutionContext*, Event*) override {
+    if (quit_closure_) {
+      std::move(quit_closure_).Run();
+    }
+  }
+
+ private:
+  base::OnceClosure quit_closure_;
+};
+
+class MockFederatedAuthRequest : public mojom::blink::FederatedAuthRequest {
+ public:
+  MockFederatedAuthRequest() = default;
+
+  MOCK_METHOD(void,
+              RequestToken,
+              (Vector<mojom::blink::IdentityProviderGetParametersPtr>,
+               mojom::blink::CredentialMediationRequirement,
+               RequestTokenCallback),
+              (override));
+  MOCK_METHOD(void,
+              RequestUserInfo,
+              (mojom::blink::IdentityProviderConfigPtr,
+               RequestUserInfoCallback),
+              (override));
+  MOCK_METHOD(void, CancelTokenRequest, (), (override));
+  MOCK_METHOD(void,
+              ResolveTokenRequest,
+              (const String&,
+               mojom::blink::FedCmRedirectMethod,
+               const std::optional<KURL>&,
+               const String&,
+               base::Value,
+               ResolveTokenRequestCallback),
+              (override));
+  MOCK_METHOD(void,
+              SetIdpSigninStatus,
+              (const scoped_refptr<const SecurityOrigin>&,
+               mojom::blink::IdpSigninStatus,
+               mojom::blink::LoginStatusOptionsPtr,
+               SetIdpSigninStatusCallback),
+              (override));
+  MOCK_METHOD(void,
+              RegisterIdP,
+              (const KURL&, RegisterIdPCallback),
+              (override));
+  MOCK_METHOD(void,
+              UnregisterIdP,
+              (const KURL&, UnregisterIdPCallback),
+              (override));
+  MOCK_METHOD(void, CloseModalDialogView, (), (override));
+  MOCK_METHOD(void,
+              Disconnect,
+              (mojom::blink::IdentityCredentialDisconnectOptionsPtr,
+               DisconnectCallback),
+              (override));
+  MOCK_METHOD(void,
+              PreventSilentAccess,
+              (PreventSilentAccessCallback),
+              (override));
+};
+
+class HTMLLoginElementClickTest : public PageTestBase {
+ public:
+  void SetUp() override {
+    EnablePlatform();
+    PageTestBase::SetUp();
+    GetFrame().GetBrowserInterfaceBroker().SetBinderForTesting(
+        mojom::blink::FederatedAuthRequest::Name_,
+        BindRepeating(
+            [](mojo::Receiver<mojom::blink::FederatedAuthRequest>* receiver,
+               mojo::ScopedMessagePipeHandle handle) {
+              receiver->Bind(
+                  mojo::PendingReceiver<mojom::blink::FederatedAuthRequest>(
+                      std::move(handle)));
+            },
+            Unretained(&receiver_)));
+  }
+
+  void TearDown() override {
+    GetFrame().GetBrowserInterfaceBroker().SetBinderForTesting(
+        mojom::blink::FederatedAuthRequest::Name_, {});
+    PageTestBase::TearDown();
+  }
+
+ protected:
+  testing::NiceMock<MockFederatedAuthRequest> mock_federated_auth_request_;
+  mojo::Receiver<mojom::blink::FederatedAuthRequest> receiver_{
+      &mock_federated_auth_request_};
+};
+
+TEST_F(HTMLLoginElementClickTest, TagName) {
   auto* element = MakeGarbageCollected<HTMLLoginElement>(GetDocument());
   EXPECT_EQ(element->tagName(), "LOGIN");
   EXPECT_EQ(element->localName(), "login");
 }
 
+TEST_F(HTMLLoginElementClickTest, ClickInitiatesFedCm) {
+  // Set a secure origin.
+  NavigateTo(KURL("https://example.com"));
+
+  auto* login = MakeGarbageCollected<HTMLLoginElement>(GetDocument());
+  GetDocument().body()->AppendChild(login);
+
+  auto* credential = MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  credential->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  credential->setAttribute(html_names::kConfigurlAttr,
+                           AtomicString("https://example.com/fedcm.json"));
+  credential->setAttribute(html_names::kClientidAttr,
+                           AtomicString("client123"));
+  login->AppendChild(credential);
+
+  EXPECT_CALL(mock_federated_auth_request_, RequestToken)
+      .WillOnce([](Vector<mojom::blink::IdentityProviderGetParametersPtr>
+                       idp_get_params,
+                   mojom::blink::CredentialMediationRequirement mediation,
+                   MockFederatedAuthRequest::RequestTokenCallback callback) {
+        ASSERT_EQ(idp_get_params.size(), 1u);
+        ASSERT_EQ(idp_get_params[0]->providers.size(), 1u);
+        EXPECT_EQ(idp_get_params[0]->providers[0]->config->config_url,
+                  "https://example.com/fedcm.json");
+        EXPECT_EQ(idp_get_params[0]->providers[0]->config->client_id,
+                  "client123");
+        std::move(callback).Run(mojom::blink::RequestTokenStatus::kSuccess,
+                                std::nullopt, base::Value("dummy-token"),
+                                nullptr, false);
+      });
+
+  // Simulate click on the login element.
+  base::RunLoop run_loop;
+  auto* listener = MakeGarbageCollected<QuitListener>(run_loop.QuitClosure());
+  login->addEventListener(event_type_names::kComplete, listener);
+  login->click();
+
+  run_loop.Run();
+
+  // Verify that the login element received the credential.
+  ScriptState* script_state =
+      ToScriptStateForMainWorld(GetDocument().GetFrame());
+  ScriptState::Scope scope(script_state);
+  ScriptValue credential_value = login->credential(script_state);
+  EXPECT_TRUE(credential_value.V8Value()->IsString());
+  EXPECT_EQ(ToCoreString(script_state->GetIsolate(),
+                         credential_value.V8Value().As<v8::String>()),
+            "dummy-token");
+}
+
+TEST_F(HTMLLoginElementClickTest, ClickInitiatesFedCmWithSimpleToken) {
+  // Set a secure origin.
+  NavigateTo(KURL("https://example.com"));
+
+  auto* login = MakeGarbageCollected<HTMLLoginElement>(GetDocument());
+  GetDocument().body()->AppendChild(login);
+
+  auto* credential = MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  credential->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  credential->setAttribute(html_names::kConfigurlAttr,
+                           AtomicString("https://example.com/fedcm.json"));
+  credential->setAttribute(html_names::kClientidAttr,
+                           AtomicString("client123"));
+  login->AppendChild(credential);
+
+  EXPECT_CALL(mock_federated_auth_request_, RequestToken)
+      .WillOnce([](Vector<mojom::blink::IdentityProviderGetParametersPtr>
+                       idp_get_params,
+                   mojom::blink::CredentialMediationRequirement mediation,
+                   MockFederatedAuthRequest::RequestTokenCallback callback) {
+        std::move(callback).Run(mojom::blink::RequestTokenStatus::kSuccess,
+                                std::nullopt, base::Value(123), nullptr, false);
+      });
+
+  // Simulate click on the login element.
+  base::RunLoop run_loop;
+  auto* listener = MakeGarbageCollected<QuitListener>(run_loop.QuitClosure());
+  login->addEventListener(event_type_names::kComplete, listener);
+  login->click();
+
+  run_loop.Run();
+
+  // Verify that the login element received the simple credential.
+  ScriptState* script_state =
+      ToScriptStateForMainWorld(GetDocument().GetFrame());
+  ScriptState::Scope scope(script_state);
+  ScriptValue credential_value = login->credential(script_state);
+  EXPECT_TRUE(credential_value.V8Value()->IsNumber());
+  EXPECT_EQ(credential_value.V8Value().As<v8::Number>()->Value(), 123);
+}
+
+TEST_F(HTMLLoginElementClickTest, NonLeftClickDoesNotInitiateFedCm) {
+  // Set a secure origin.
+  NavigateTo(KURL("https://example.com"));
+
+  auto* login = MakeGarbageCollected<HTMLLoginElement>(GetDocument());
+  GetDocument().body()->AppendChild(login);
+
+  auto* credential = MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  credential->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  credential->setAttribute(html_names::kConfigurlAttr,
+                           AtomicString("https://example.com/fedcm.json"));
+  credential->setAttribute(html_names::kClientidAttr,
+                           AtomicString("client123"));
+  login->AppendChild(credential);
+
+  EXPECT_CALL(mock_federated_auth_request_, RequestToken).Times(0);
+
+  // Simulate a right-click.
+  ScriptState* script_state =
+      ToScriptStateForMainWorld(GetDocument().GetFrame());
+  ScriptState::Scope scope(script_state);
+
+  PointerEventInit* init = PointerEventInit::Create();
+  init->setButton(2);  // Right button
+  init->setPointerId(1);
+  PointerEvent* event = PointerEvent::Create(event_type_names::kClick, init);
+  login->DispatchEvent(*event);
+}
+
+TEST_F(HTMLLoginElementClickTest, EnterKeyInitiatesFedCm) {
+  // Set a secure origin.
+  NavigateTo(KURL("https://example.com"));
+
+  auto* login = MakeGarbageCollected<HTMLLoginElement>(GetDocument());
+  login->setAttribute(html_names::kTabindexAttr, AtomicString("0"));
+  GetDocument().body()->AppendChild(login);
+  login->Focus();
+  ASSERT_EQ(GetDocument().FocusedElement(), login);
+
+  auto* credential = MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  credential->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  credential->setAttribute(html_names::kConfigurlAttr,
+                           AtomicString("https://example.com/fedcm.json"));
+  credential->setAttribute(html_names::kClientidAttr,
+                           AtomicString("client123"));
+  login->AppendChild(credential);
+
+  EXPECT_CALL(mock_federated_auth_request_, RequestToken)
+      .WillOnce([](Vector<mojom::blink::IdentityProviderGetParametersPtr>
+                       idp_get_params,
+                   mojom::blink::CredentialMediationRequirement mediation,
+                   MockFederatedAuthRequest::RequestTokenCallback callback) {
+        std::move(callback).Run(mojom::blink::RequestTokenStatus::kSuccess,
+                                std::nullopt, base::Value("dummy-token"),
+                                nullptr, false);
+      });
+
+  // Simulate Enter keydown.
+  ScriptState* script_state =
+      ToScriptStateForMainWorld(GetDocument().GetFrame());
+  ScriptState::Scope scope(script_state);
+
+  KeyboardEventInit* init = KeyboardEventInit::Create();
+  init->setKey("Enter");
+  KeyboardEvent* event =
+      KeyboardEvent::Create(script_state, event_type_names::kKeydown, init);
+  base::RunLoop run_loop;
+  auto* listener = MakeGarbageCollected<QuitListener>(run_loop.QuitClosure());
+  login->addEventListener(event_type_names::kComplete, listener);
+  login->DispatchEvent(*event);
+
+  run_loop.Run();
+}
+
+TEST_F(HTMLLoginElementClickTest, InsecureContextDoesNotInitiateFedCm) {
+  // Navigate to an insecure origin.
+  NavigateTo(KURL("http://example.com"));
+
+  ConsoleMessageStorage& storage = GetPage().GetConsoleMessageStorage();
+  wtf_size_t initial_size = storage.size();
+
+  auto* login = MakeGarbageCollected<HTMLLoginElement>(GetDocument());
+  GetDocument().body()->AppendChild(login);
+
+  // Check that a console message was emitted immediately on insertion.
+  EXPECT_EQ(storage.size(), initial_size + 1);
+  EXPECT_TRUE(
+      storage.at(storage.size() - 1)->Message().contains("secure context"));
+
+  auto* credential = MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  credential->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  credential->setAttribute(html_names::kConfigurlAttr,
+                           AtomicString("https://example.com/fedcm.json"));
+  credential->setAttribute(html_names::kClientidAttr,
+                           AtomicString("client123"));
+  login->AppendChild(credential);
+
+  EXPECT_CALL(mock_federated_auth_request_, RequestToken).Times(0);
+
+  // Simulate click.
+  login->click();
+
+  // Ensure no additional message is emitted on click.
+  EXPECT_EQ(storage.size(), initial_size + 1);
+}
+
+TEST_F(HTMLLoginElementClickTest,
+       PermissionsPolicyDisabledDoesNotInitiateFedCm) {
+  // Set a secure origin but disable the permissions policy.
+  NavigateTo(KURL("https://example.com"));
+
+  ExecutionContext* context = GetDocument().GetExecutionContext();
+  context->GetSecurityContext().SetPermissionsPolicy(
+      network::PermissionsPolicy::CreateFromParsedPolicy(
+          {}, context->GetSecurityOrigin()->ToUrlOrigin()));
+
+  ConsoleMessageStorage& storage = GetPage().GetConsoleMessageStorage();
+  wtf_size_t initial_size = storage.size();
+
+  auto* login = MakeGarbageCollected<HTMLLoginElement>(GetDocument());
+  GetDocument().body()->AppendChild(login);
+
+  // No console message on insertion for permissions policy.
+  EXPECT_EQ(storage.size(), initial_size);
+
+  auto* credential = MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  credential->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  credential->setAttribute(html_names::kConfigurlAttr,
+                           AtomicString("https://example.com/fedcm.json"));
+  credential->setAttribute(html_names::kClientidAttr,
+                           AtomicString("client123"));
+  login->AppendChild(credential);
+
+  EXPECT_CALL(mock_federated_auth_request_, RequestToken).Times(0);
+
+  // Simulate click.
+  login->click();
+
+  // Check that a console message was emitted on click.
+  EXPECT_EQ(storage.size(), initial_size + 1);
+  EXPECT_TRUE(storage.at(storage.size() - 1)
+                  ->Message()
+                  .contains("permissions policy is not enabled"));
+}
+
+TEST_F(HTMLLoginElementClickTest, CSPConnectSrcBlocksIDP) {
+  // Set a secure origin.
+  NavigateTo(KURL("https://example.com"));
+
+  ExecutionContext* context = GetDocument().GetExecutionContext();
+
+  // Set up CSP to block one of the IDPs.
+  context->GetContentSecurityPolicy()->AddPolicies(ParseContentSecurityPolicies(
+      "connect-src https://allowed.com",
+      network::mojom::blink::ContentSecurityPolicyType::kEnforce,
+      network::mojom::blink::ContentSecurityPolicySource::kHTTP,
+      *(context->GetSecurityOrigin())));
+
+  auto* login = MakeGarbageCollected<HTMLLoginElement>(GetDocument());
+  GetDocument().body()->AppendChild(login);
+
+  // Add an allowed IDP.
+  auto* credential1 =
+      MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  credential1->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  credential1->setAttribute(html_names::kConfigurlAttr,
+                            AtomicString("https://allowed.com/fedcm.json"));
+  credential1->setAttribute(html_names::kClientidAttr, AtomicString("123"));
+  login->AppendChild(credential1);
+
+  // Add a blocked IDP.
+  auto* credential2 =
+      MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  credential2->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  credential2->setAttribute(html_names::kConfigurlAttr,
+                            AtomicString("https://blocked.com/fedcm.json"));
+  credential2->setAttribute(html_names::kClientidAttr, AtomicString("456"));
+  login->AppendChild(credential2);
+
+  EXPECT_CALL(mock_federated_auth_request_, RequestToken)
+      .WillOnce([](Vector<mojom::blink::IdentityProviderGetParametersPtr>
+                       idp_get_params,
+                   mojom::blink::CredentialMediationRequirement mediation,
+                   MockFederatedAuthRequest::RequestTokenCallback callback) {
+        // Verify that only the allowed IDP was included in the request.
+        ASSERT_EQ(idp_get_params.size(), 1u);
+        EXPECT_EQ(idp_get_params[0]->providers[0]->config->config_url,
+                  "https://allowed.com/fedcm.json");
+        std::move(callback).Run(mojom::blink::RequestTokenStatus::kSuccess,
+                                std::nullopt, base::Value("dummy-token"),
+                                nullptr, false);
+      });
+
+  // Simulate click.
+  base::RunLoop run_loop;
+  auto* listener = MakeGarbageCollected<QuitListener>(run_loop.QuitClosure());
+  login->addEventListener(event_type_names::kComplete, listener);
+  login->click();
+
+  run_loop.Run();
+}
+
+TEST_F(HTMLLoginElementClickTest, IsFocusable) {
+  auto* login = MakeGarbageCollected<HTMLLoginElement>(GetDocument());
+  GetDocument().body()->AppendChild(login);
+
+  // Initially not focusable without credentials.
+  EXPECT_FALSE(login->IsFocusable());
+
+  auto* credential = MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  credential->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  credential->setAttribute(html_names::kConfigurlAttr,
+                           AtomicString("https://example.com/fedcm.json"));
+  credential->setAttribute(html_names::kClientidAttr,
+                           AtomicString("client123"));
+  login->AppendChild(credential);
+
+  // Focusable after adding a valid credential.
+  EXPECT_TRUE(login->IsFocusable());
+}
+
+TEST_F(HTMLLoginElementClickTest, ClickWithInvalidParamsDoesNotInitiateFedCm) {
+  // Set a secure origin.
+  NavigateTo(KURL("https://example.com"));
+
+  auto* login = MakeGarbageCollected<HTMLLoginElement>(GetDocument());
+  GetDocument().body()->AppendChild(login);
+
+  auto* credential = MakeGarbageCollected<HTMLCredentialElement>(GetDocument());
+  credential->setAttribute(html_names::kTypeAttr, AtomicString("federated"));
+  credential->setAttribute(html_names::kConfigurlAttr,
+                           AtomicString("https://example.com/fedcm.json"));
+  credential->setAttribute(html_names::kClientidAttr,
+                           AtomicString("client123"));
+  // Set invalid JSON in params.
+  credential->setAttribute(html_names::kParamsAttr,
+                           AtomicString("{invalid: json}"));
+  login->AppendChild(credential);
+
+  EXPECT_CALL(mock_federated_auth_request_, RequestToken).Times(0);
+
+  // Simulate click.
+  login->click();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/fragment_builder.cc b/third_party/blink/renderer/core/layout/fragment_builder.cc
index a5d7f58..52be88b 100644
--- a/third_party/blink/renderer/core/layout/fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/fragment_builder.cc
@@ -31,16 +31,6 @@
              node.Style().GetPosition());
 }
 
-PhysicalAxes StickyConstrainedAxes(const ComputedStyle& style) {
-  PhysicalAxes axes = kPhysicalAxesNone;
-  if (!style.Top().IsAuto() || !style.Bottom().IsAuto()) {
-    axes |= kPhysicalAxesVertical;
-  }
-  if (!style.Left().IsAuto() || !style.Right().IsAuto()) {
-    axes |= kPhysicalAxesHorizontal;
-  }
-  return axes;
-}
 }  // namespace
 
 AnchorMap::SetOptions FragmentBuilder::AnchorOptionsForChild(
@@ -143,7 +133,8 @@
   const PhysicalAxes scrollable_axes = GetOverflowScrollAxes();
 
   if (child.HasStickyConstrainedPosition()) {
-    const PhysicalAxes axes = StickyConstrainedAxes(child.Style());
+    const PhysicalAxes axes =
+        LayoutBoxModelObject::StickyConstrainedAxes(child.Style());
     const PhysicalAxes consumed = scrollable_axes & axes;
     const PhysicalAxes pending = axes ^ consumed;
 
diff --git a/third_party/blink/renderer/core/layout/geometry/axis.h b/third_party/blink/renderer/core/layout/geometry/axis.h
index 4c4e582..6fd2e81 100644
--- a/third_party/blink/renderer/core/layout/geometry/axis.h
+++ b/third_party/blink/renderer/core/layout/geometry/axis.h
@@ -17,8 +17,14 @@
 enum class LogicalAxis : uint8_t { kInline = 0b01, kBlock = 0b10 };
 enum class PhysicalAxis : uint8_t { kHorizontal = 0b01, kVertical = 0b10 };
 
-using PhysicalAxes = base::StrongAlias<class PhysicalAxesTag, uint8_t>;
-using LogicalAxes = base::StrongAlias<class LogicalAxesTag, uint8_t>;
+struct PhysicalAxes : public base::StrongAlias<class PhysicalAxesTag, uint8_t> {
+  using StrongAlias::StrongAlias;
+  explicit constexpr operator bool() const { return value() != 0; }
+};
+struct LogicalAxes : public base::StrongAlias<class LogicalAxesTag, uint8_t> {
+  using StrongAlias::StrongAlias;
+  explicit constexpr operator bool() const { return value() != 0; }
+};
 
 inline constexpr LogicalAxes operator|(LogicalAxes a, LogicalAxes b) {
   return LogicalAxes(a.value() | b.value());
diff --git a/third_party/blink/renderer/core/layout/layout_box_model_object.cc b/third_party/blink/renderer/core/layout/layout_box_model_object.cc
index cdbd0ed..b5451571 100644
--- a/third_party/blink/renderer/core/layout/layout_box_model_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_box_model_object.cc
@@ -180,10 +180,18 @@
     Parent()->SetNeedsLayout(layout_invalidation_reason::kChildChanged,
                              kMarkContainerChain);
 
-  // Clear our sticky constraints if we are no longer sticky.
-  if (Layer() && old_style->HasStickyConstrainedPosition() &&
-      !StyleRef().HasStickyConstrainedPosition()) {
-    SetStickyConstraints(nullptr);
+  if (Layer() && old_style->HasStickyConstrainedPosition()) {
+    // Clear our sticky constraints if we are no longer sticky.
+    if (!StyleRef().HasStickyConstrainedPosition()) {
+      ClearStickyConstraints(kPhysicalAxesBoth);
+    } else {
+      // When still sticky, clear out axes that no longer exist.
+      const PhysicalAxes old_axes = StickyConstrainedAxes(*old_style);
+      const PhysicalAxes new_axes = StickyConstrainedAxes(StyleRef());
+      if (const PhysicalAxes remove_axes = old_axes ^ (old_axes & new_axes)) {
+        ClearStickyConstraints(remove_axes);
+      }
+    }
   }
 
   PaintLayerType type = LayerTypeRequired();
@@ -519,14 +527,24 @@
   return ContainingBlock();
 }
 
-StickyPositionScrollingConstraints*
-LayoutBoxModelObject::ComputeStickyPositionConstraints() const {
+StickyConstraintsData LayoutBoxModelObject::ComputeStickyPositionConstraints(
+    const PaintLayer& scroll_container_layer,
+    PhysicalAxes scroll_axes) const {
   NOT_DESTROYED();
   DCHECK(StyleRef().HasStickyConstrainedPosition());
 
   bool is_fixed_to_view = false;
-  const auto* scroll_container_layer =
-      Layer()->ContainingScrollContainerLayer(&is_fixed_to_view);
+  {
+    // Walk up layout / paint layer tree to find if a fixed to view element
+    // exists between this element and `scroll_container_layer`.
+    const PaintLayer* walk = Layer();
+    while (walk && walk != &scroll_container_layer && !is_fixed_to_view) {
+      walk = walk->ContainingScrollContainerLayer(&is_fixed_to_view);
+    }
+    // We should reach `scroll_container_layer` unless we hit a fixed to view
+    // ancestor (in which case we stop early).
+    CHECK(walk == &scroll_container_layer || is_fixed_to_view);
+  }
 
   // Skip anonymous containing blocks except for anonymous fieldset content box.
   LayoutBlock* sticky_container = StickyContainer();
@@ -538,7 +556,7 @@
     sticky_container = sticky_container->ContainingBlock();
   }
 
-  const auto* scroll_container = scroll_container_layer->GetLayoutBox();
+  const auto* scroll_container = scroll_container_layer.GetLayoutBox();
   DCHECK(scroll_container);
   const PhysicalOffset scroll_container_border_offset(
       scroll_container->BorderLeft(), scroll_container->BorderTop());
@@ -626,10 +644,16 @@
   const PhysicalRect constraining_rect =
       scroll_container->ComputeStickyConstrainingRect();
 
-  auto compute_axis_data = [&](PhysicalAxis axis, const Length& min_length,
-                               const Length& max_length,
-                               LayoutUnit available_size,
-                               LayoutUnit sticky_box_size, bool is_flipped) {
+  auto compute_axis_data =
+      [&](PhysicalAxis axis, const Length& min_length, const Length& max_length,
+          LayoutUnit available_size, LayoutUnit sticky_box_size,
+          bool is_flipped) -> StickyPositionScrollingConstraints::PerAxisData* {
+    const PhysicalAxes axes = axis == PhysicalAxis::kHorizontal
+                                  ? kPhysicalAxesHorizontal
+                                  : kPhysicalAxesVertical;
+    if (!(axes & scroll_axes)) {
+      return nullptr;
+    }
     std::optional<LayoutUnit> min_inset;
     std::optional<LayoutUnit> max_inset;
 
@@ -658,7 +682,7 @@
         axis, scroll_container_relative_containing_block_rect,
         scroll_container_relative_sticky_box_rect, constraining_rect,
         nearest_sticky_layer_shifting_sticky_box,
-        nearest_sticky_layer_shifting_containing_block, scroll_container_layer,
+        nearest_sticky_layer_shifting_containing_block, &scroll_container_layer,
         is_fixed_to_view, min_inset, max_inset);
   };
 
@@ -666,21 +690,60 @@
   const WritingDirectionMode sticky_container_writing_direction =
       sticky_container->StyleRef().GetWritingDirection();
 
-  return MakeGarbageCollected<StickyPositionScrollingConstraints>(
+  return StickyConstraintsData{
       compute_axis_data(PhysicalAxis::kHorizontal, style.Left(), style.Right(),
                         constraining_rect.size.width, sticky_box_rect.Width(),
                         sticky_container_writing_direction.IsFlippedX()),
       compute_axis_data(PhysicalAxis::kVertical, style.Top(), style.Bottom(),
                         constraining_rect.size.height, sticky_box_rect.Height(),
-                        sticky_container_writing_direction.IsFlippedY()));
+                        sticky_container_writing_direction.IsFlippedY())};
+}
+
+void LayoutBoxModelObject::SetStickyConstraints(
+    StickyConstraintsData constraints) {
+  NOT_DESTROYED();
+
+  if (GetMutableForPainting().FirstFragment().SetStickyConstraints(
+          constraints)) {
+    SetNeedsPaintPropertyUpdate();
+  }
+}
+
+void LayoutBoxModelObject::ClearStickyConstraints(PhysicalAxes axes_to_clear) {
+  NOT_DESTROYED();
+  if (GetMutableForPainting().FirstFragment().ClearStickyConstraints(
+          axes_to_clear)) {
+    SetNeedsPaintPropertyUpdate();
+  }
+}
+
+PhysicalAxes LayoutBoxModelObject::StickyConstrainedAxes(
+    const ComputedStyle& style) {
+  if (style.GetPosition() != EPosition::kSticky) {
+    return kPhysicalAxesNone;
+  }
+  PhysicalAxes axes = kPhysicalAxesNone;
+  if (!style.Top().IsAuto() || !style.Bottom().IsAuto()) {
+    axes |= kPhysicalAxesVertical;
+  }
+  if (!style.Left().IsAuto() || !style.Right().IsAuto()) {
+    axes |= kPhysicalAxesHorizontal;
+  }
+  // TODO(crbug.com/481019005): When disabled, this forces both sticky axes to
+  // propagate upwards if either axis is active. This preserves existing
+  // compositor behavior and can be removed once the compositor supports
+  // multiple scroll container parents.
+  if (!RuntimeEnabledFeatures::SingleAxisScrollContainersEnabled() && axes) {
+    axes = kPhysicalAxesBoth;
+  }
+  return axes;
 }
 
 PhysicalOffset LayoutBoxModelObject::StickyPositionOffset() const {
   NOT_DESTROYED();
   // TODO(chrishtr): StickyPositionOffset depends data updated after layout at
   // present, but there are callsites within Layout for it.
-  auto* constraints = StickyConstraints();
-  return constraints ? constraints->StickyOffset() : PhysicalOffset();
+  return StickyConstraints().StickyOffset();
 }
 
 PhysicalOffset LayoutBoxModelObject::OffsetFromContainerInternal(
diff --git a/third_party/blink/renderer/core/layout/layout_box_model_object.h b/third_party/blink/renderer/core/layout/layout_box_model_object.h
index 197f1ec6..3123e13b 100644
--- a/third_party/blink/renderer/core/layout/layout_box_model_object.h
+++ b/third_party/blink/renderer/core/layout/layout_box_model_object.h
@@ -29,6 +29,7 @@
 #include "third_party/blink/renderer/core/layout/background_bleed_avoidance.h"
 #include "third_party/blink/renderer/core/layout/content_change_type.h"
 #include "third_party/blink/renderer/core/layout/layout_object.h"
+#include "third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.h"
 #include "third_party/blink/renderer/platform/text/writing_mode_utils.h"
 
 namespace blink {
@@ -120,20 +121,27 @@
   void DestroyLayer();
 
   // Computes the sticky constraints for this object.
-  StickyPositionScrollingConstraints* ComputeStickyPositionConstraints() const;
+  StickyConstraintsData ComputeStickyPositionConstraints(
+      const PaintLayer& scroll_container_layer,
+      PhysicalAxes scroll_axes) const;
 
   PhysicalOffset StickyPositionOffset() const;
   virtual LayoutBlock* StickyContainer() const;
 
-  StickyPositionScrollingConstraints* StickyConstraints() const {
+  StickyPositionScrollingConstraints StickyConstraints() const {
     NOT_DESTROYED();
     return FirstFragment().StickyConstraints();
   }
-  void SetStickyConstraints(StickyPositionScrollingConstraints* constraints) {
+  bool HasStickyConstraints() const {
     NOT_DESTROYED();
-    GetMutableForPainting().FirstFragment().SetStickyConstraints(constraints);
-    SetNeedsPaintPropertyUpdate();
+    return FirstFragment().HasStickyConstraints();
   }
+  void SetStickyConstraints(StickyConstraintsData constraints);
+  void ClearStickyConstraints(PhysicalAxes axes_to_clear);
+
+  // Determines which physical axes are actively constrained by sticky
+  // positioning.
+  static PhysicalAxes StickyConstrainedAxes(const ComputedStyle& style);
 
   // IE extensions. Used to calculate offsetWidth/Height.
   virtual LayoutUnit OffsetLeft(const Element*) const = 0;
diff --git a/third_party/blink/renderer/core/layout/layout_box_model_object_test.cc b/third_party/blink/renderer/core/layout/layout_box_model_object_test.cc
index 0eceeec..53d2842 100644
--- a/third_party/blink/renderer/core/layout/layout_box_model_object_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_box_model_object_test.cc
@@ -225,23 +225,23 @@
   ASSERT_EQ(scroller->Layer(),
             sticky->Layer()->ContainingScrollContainerLayer());
 
-  const auto* constraints = sticky->StickyConstraints();
+  const auto constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_TRUE(HasStickyLayer(scrollable_area, sticky));
-  ASSERT_EQ(0.f, constraints->TopInset()->ToFloat());
+  ASSERT_EQ(0.f, constraints.TopInset()->ToFloat());
 
   // The coordinates of the constraint rects should all be with respect to the
   // unscrolled scroller.
   ASSERT_EQ(gfx::Rect(15, 115, 170, 370),
             ToEnclosingRect(
-                constraints->ScrollContainerRelativeContainingBlockRect()));
+                constraints.ScrollContainerRelativeContainingBlockRect()));
   ASSERT_EQ(
       gfx::Rect(15, 115, 100, 100),
-      ToEnclosingRect(constraints->ScrollContainerRelativeStickyBoxRect()));
+      ToEnclosingRect(constraints.ScrollContainerRelativeStickyBoxRect()));
 
   // The sticky constraining rect also doesn't include the border offset.
   ASSERT_EQ(gfx::Rect(0, 0, 400, 100),
-            ToEnclosingRect(constraints->ConstrainingRect()));
+            ToEnclosingRect(constraints.ConstrainingRect()));
 }
 
 // Verifies that the sticky constraints are correctly computed in right to left.
@@ -267,7 +267,7 @@
   ASSERT_EQ(scroller->Layer(),
             sticky->Layer()->ContainingScrollContainerLayer());
 
-  const auto* constraints = sticky->StickyConstraints();
+  const auto constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_TRUE(HasStickyLayer(scrollable_area, sticky));
 
@@ -275,14 +275,14 @@
   // unscrolled scroller.
   ASSERT_EQ(gfx::Rect(215, 115, 170, 370),
             ToEnclosingRect(
-                constraints->ScrollContainerRelativeContainingBlockRect()));
+                constraints.ScrollContainerRelativeContainingBlockRect()));
   ASSERT_EQ(
       gfx::Rect(285, 115, 100, 100),
-      ToEnclosingRect(constraints->ScrollContainerRelativeStickyBoxRect()));
+      ToEnclosingRect(constraints.ScrollContainerRelativeStickyBoxRect()));
 
   // The sticky constraining rect also doesn't include the border offset.
   ASSERT_EQ(gfx::Rect(0, 0, 400, 100),
-            ToEnclosingRect(constraints->ConstrainingRect()));
+            ToEnclosingRect(constraints.ConstrainingRect()));
 }
 
 // Verifies that the sticky constraints are correctly computed for inline.
@@ -317,21 +317,21 @@
   EXPECT_EQ(scroller->Layer(),
             sticky->Layer()->ContainingScrollContainerLayer());
 
-  const auto* constraints = sticky->StickyConstraints();
+  const auto constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_TRUE(HasStickyLayer(scrollable_area, sticky));
-  EXPECT_EQ(10.f, constraints->TopInset()->ToFloat());
+  EXPECT_EQ(10.f, constraints.TopInset()->ToFloat());
 
   // The coordinates of the constraint rects should all be with respect to the
   // unscrolled scroller.
   ASSERT_EQ(gfx::Rect(0, 100, 200, 400),
             ToEnclosingRect(
-                constraints->ScrollContainerRelativeContainingBlockRect()));
+                constraints.ScrollContainerRelativeContainingBlockRect()));
   ASSERT_EQ(
       gfx::Rect(0, 100, 10, 10),
-      ToEnclosingRect(constraints->ScrollContainerRelativeStickyBoxRect()));
+      ToEnclosingRect(constraints.ScrollContainerRelativeStickyBoxRect()));
   ASSERT_EQ(gfx::Rect(0, 0, 100, 100),
-            ToEnclosingRect(constraints->ConstrainingRect()));
+            ToEnclosingRect(constraints.ConstrainingRect()));
 }
 
 // Verifies that the sticky constraints are correctly computed for sticky with
@@ -373,21 +373,21 @@
   EXPECT_EQ(scroller->Layer(),
             sticky->Layer()->ContainingScrollContainerLayer());
 
-  const auto* constraints = sticky->StickyConstraints();
+  const auto constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_TRUE(HasStickyLayer(scrollable_area, sticky));
-  EXPECT_EQ(10.f, constraints->TopInset()->ToFloat());
+  EXPECT_EQ(10.f, constraints.TopInset()->ToFloat());
 
   // The coordinates of the constraint rects should all be with respect to the
   // unscrolled scroller, and not corrected for scroll origin.
   ASSERT_EQ(gfx::Rect(-100, 100, 200, 400),
             ToEnclosingRect(
-                constraints->ScrollContainerRelativeContainingBlockRect()));
+                constraints.ScrollContainerRelativeContainingBlockRect()));
   ASSERT_EQ(
       gfx::Rect(90, 100, 10, 10),
-      ToEnclosingRect(constraints->ScrollContainerRelativeStickyBoxRect()));
+      ToEnclosingRect(constraints.ScrollContainerRelativeStickyBoxRect()));
   ASSERT_EQ(gfx::Rect(-2100, 0, 100, 100),
-            ToEnclosingRect(constraints->ConstrainingRect()));
+            ToEnclosingRect(constraints.ConstrainingRect()));
 }
 
 // Verifies that the sticky constraints are not affected by transforms
@@ -413,19 +413,19 @@
   ASSERT_EQ(scroller->Layer(),
             sticky->Layer()->ContainingScrollContainerLayer());
 
-  const auto* constraints = sticky->StickyConstraints();
+  const auto constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_TRUE(HasStickyLayer(scrollable_area, sticky));
-  ASSERT_EQ(0.f, constraints->TopInset()->ToFloat());
+  ASSERT_EQ(0.f, constraints.TopInset()->ToFloat());
 
   // The coordinates of the constraint rects should all be with respect to the
   // unscrolled scroller.
   ASSERT_EQ(gfx::Rect(15, 115, 170, 370),
             ToEnclosingRect(
-                constraints->ScrollContainerRelativeContainingBlockRect()));
+                constraints.ScrollContainerRelativeContainingBlockRect()));
   ASSERT_EQ(
       gfx::Rect(15, 115, 100, 100),
-      ToEnclosingRect(constraints->ScrollContainerRelativeStickyBoxRect()));
+      ToEnclosingRect(constraints.ScrollContainerRelativeStickyBoxRect()));
 }
 
 // Verifies that the sticky constraints are correctly computed.
@@ -450,23 +450,23 @@
   ASSERT_EQ(scroller->Layer(),
             sticky->Layer()->ContainingScrollContainerLayer());
 
-  const auto* constraints = sticky->StickyConstraints();
+  const auto constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_TRUE(HasStickyLayer(scrollable_area, sticky));
-  ASSERT_EQ(0.f, constraints->TopInset()->ToFloat());
+  ASSERT_EQ(0.f, constraints.TopInset()->ToFloat());
 
   if (RuntimeEnabledFeatures::LayoutIgnoreMarginsForStickyEnabled()) {
     ASSERT_EQ(gfx::Rect(25, 125, 200, 350),
               ToEnclosingRect(
-                  constraints->ScrollContainerRelativeContainingBlockRect()));
+                  constraints.ScrollContainerRelativeContainingBlockRect()));
   } else {
     ASSERT_EQ(gfx::Rect(25, 145, 200, 330),
               ToEnclosingRect(
-                  constraints->ScrollContainerRelativeContainingBlockRect()));
+                  constraints.ScrollContainerRelativeContainingBlockRect()));
   }
   ASSERT_EQ(
       gfx::Rect(25, 145, 100, 100),
-      ToEnclosingRect(constraints->ScrollContainerRelativeStickyBoxRect()));
+      ToEnclosingRect(constraints.ScrollContainerRelativeStickyBoxRect()));
 }
 
 // Verifies that the sticky constraints are correct when the sticky position
@@ -490,15 +490,15 @@
   ASSERT_EQ(scroller->Layer(),
             sticky->Layer()->ContainingScrollContainerLayer());
 
-  const auto* constraints = sticky->StickyConstraints();
+  const auto constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_TRUE(HasStickyLayer(scrollable_area, sticky));
   ASSERT_EQ(gfx::Rect(0, 0, 400, 1100),
             ToEnclosingRect(
-                constraints->ScrollContainerRelativeContainingBlockRect()));
+                constraints.ScrollContainerRelativeContainingBlockRect()));
   ASSERT_EQ(
       gfx::Rect(0, 0, 100, 100),
-      ToEnclosingRect(constraints->ScrollContainerRelativeStickyBoxRect()));
+      ToEnclosingRect(constraints.ScrollContainerRelativeStickyBoxRect()));
 }
 
 // Verifies that the sticky constraints are correct when the sticky position
@@ -525,16 +525,16 @@
   ASSERT_EQ(scroller->Layer(),
             sticky->Layer()->ContainingScrollContainerLayer());
 
-  const auto* constraints = sticky->StickyConstraints();
+  const auto constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_TRUE(HasStickyLayer(scrollable_area, sticky));
 
   ASSERT_EQ(gfx::Rect(15, 115, 170, 370),
             ToEnclosingRect(
-                constraints->ScrollContainerRelativeContainingBlockRect()));
+                constraints.ScrollContainerRelativeContainingBlockRect()));
   ASSERT_EQ(
       gfx::Rect(15, 165, 100, 100),
-      ToEnclosingRect(constraints->ScrollContainerRelativeStickyBoxRect()));
+      ToEnclosingRect(constraints.ScrollContainerRelativeStickyBoxRect()));
 }
 
 TEST_P(LayoutBoxModelObjectTest, StickyPositionTableContainers) {
@@ -554,16 +554,16 @@
   PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
   auto* sticky = GetLayoutBoxModelObjectByElementId("sticky");
 
-  const auto* constraints = sticky->StickyConstraints();
+  const auto constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_TRUE(HasStickyLayer(scrollable_area, sticky));
 
   ASSERT_EQ(gfx::Rect(0, 0, 50, 100),
             ToEnclosingRect(
-                constraints->ScrollContainerRelativeContainingBlockRect()));
+                constraints.ScrollContainerRelativeContainingBlockRect()));
   ASSERT_EQ(
       gfx::Rect(0, 50, 50, 50),
-      ToEnclosingRect(constraints->ScrollContainerRelativeStickyBoxRect()));
+      ToEnclosingRect(constraints.ScrollContainerRelativeStickyBoxRect()));
 }
 
 // Tests that when a non-layer changes size it invalidates the constraints for
@@ -590,19 +590,19 @@
   auto* sticky = GetLayoutBoxModelObjectByElementId("sticky");
   auto* target = GetLayoutBoxModelObjectByElementId("target");
 
-  const auto* constraints = sticky->StickyConstraints();
+  const auto constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_TRUE(HasStickyLayer(scrollable_area, sticky));
 
   EXPECT_EQ(25.f,
-            constraints->ScrollContainerRelativeStickyBoxRect().X().ToFloat());
+            constraints.ScrollContainerRelativeStickyBoxRect().X().ToFloat());
 
   To<HTMLElement>(target->GetNode())->classList().Add(AtomicString("hide"));
   // After updating layout we should have the updated position.
   GetDocument().View()->UpdateLifecycleToLayoutClean(
       DocumentUpdateReason::kTest);
   EXPECT_EQ(50.f, sticky->StickyConstraints()
-                      ->ScrollContainerRelativeStickyBoxRect()
+                      .ScrollContainerRelativeStickyBoxRect()
                       .X()
                       .ToFloat());
 }
@@ -732,33 +732,33 @@
 
   ASSERT_TRUE(
       HasStickyLayer(scrollable_area, sticky_outer_div->GetLayoutBox()));
-  auto* outer_div_constraints =
+  auto outer_div_constraints =
       sticky_outer_div->GetLayoutObject().StickyConstraints();
   ASSERT_TRUE(outer_div_constraints);
 
   ASSERT_TRUE(HasStickyLayer(scrollable_area, sticky_outer_inline));
-  auto* outer_inline_constraints = sticky_outer_inline->StickyConstraints();
+  auto outer_inline_constraints = sticky_outer_inline->StickyConstraints();
   ASSERT_TRUE(outer_inline_constraints);
 
   ASSERT_FALSE(HasStickyLayer(scrollable_area, unanchored_sticky));
   EXPECT_FALSE(unanchored_sticky->StickyConstraints());
 
   ASSERT_TRUE(HasStickyLayer(scrollable_area, sticky_inner_inline));
-  auto* inner_inline_constraints = sticky_inner_inline->StickyConstraints();
+  auto inner_inline_constraints = sticky_inner_inline->StickyConstraints();
   ASSERT_TRUE(inner_inline_constraints);
 
   // The outer block element trivially has no sticky-box shifting ancestor.
-  EXPECT_FALSE(outer_div_constraints->NearestStickyLayerShiftingStickyBox());
+  EXPECT_FALSE(outer_div_constraints.NearestStickyLayerShiftingStickyBox());
 
   // Neither does the outer inline element, as its parent element is also its
   // containing block.
-  EXPECT_FALSE(outer_inline_constraints->NearestStickyLayerShiftingStickyBox());
+  EXPECT_FALSE(outer_inline_constraints.NearestStickyLayerShiftingStickyBox());
 
   // However the inner inline element does have a sticky-box shifting ancestor,
   // as its containing block is the ancestor block element, above its ancestor
   // sticky element.
   EXPECT_EQ(sticky_outer_inline,
-            inner_inline_constraints->NearestStickyLayerShiftingStickyBox());
+            inner_inline_constraints.NearestStickyLayerShiftingStickyBox());
 }
 
 // Verifies that the correct containing-block shifting ancestor is found when
@@ -797,29 +797,29 @@
   EXPECT_TRUE(scroller->StickyConstraints());
 
   ASSERT_TRUE(HasStickyLayer(scrollable_area, sticky_parent));
-  auto* parent_constraints = sticky_parent->StickyConstraints();
+  auto parent_constraints = sticky_parent->StickyConstraints();
   ASSERT_TRUE(parent_constraints);
 
   ASSERT_TRUE(HasStickyLayer(scrollable_area, sticky_child));
-  auto* child_constraints = sticky_child->StickyConstraints();
+  auto child_constraints = sticky_child->StickyConstraints();
   ASSERT_TRUE(child_constraints);
 
   ASSERT_TRUE(HasStickyLayer(scrollable_area, sticky_nested_child));
-  auto* nested_child_constraints = sticky_nested_child->StickyConstraints();
+  auto nested_child_constraints = sticky_nested_child->StickyConstraints();
   ASSERT_TRUE(nested_child_constraints);
 
   // The outer <div> should not detect the scroller as its containing-block
   // shifting ancestor.
-  EXPECT_FALSE(parent_constraints->NearestStickyLayerShiftingContainingBlock());
+  EXPECT_FALSE(parent_constraints.NearestStickyLayerShiftingContainingBlock());
 
   // Both inner children should detect the parent <div> as their
   // containing-block shifting ancestor. They skip past unanchored sticky
   // because it will never have a non-zero offset.
   EXPECT_EQ(sticky_parent,
-            child_constraints->NearestStickyLayerShiftingContainingBlock());
+            child_constraints.NearestStickyLayerShiftingContainingBlock());
   EXPECT_EQ(
       sticky_parent,
-      nested_child_constraints->NearestStickyLayerShiftingContainingBlock());
+      nested_child_constraints.NearestStickyLayerShiftingContainingBlock());
 }
 
 // Verifies that the correct containing-block shifting ancestor is found when
@@ -849,14 +849,13 @@
   EXPECT_TRUE(sticky_parent->StickyConstraints());
 
   ASSERT_TRUE(HasStickyLayer(scrollable_area, sticky_grandchild));
-  auto* grandchild_constraints = sticky_grandchild->StickyConstraints();
+  auto grandchild_constraints = sticky_grandchild->StickyConstraints();
   ASSERT_TRUE(grandchild_constraints);
 
   // The grandchild sticky should detect the parent as its containing-block
   // shifting ancestor.
-  EXPECT_EQ(
-      sticky_parent,
-      grandchild_constraints->NearestStickyLayerShiftingContainingBlock());
+  EXPECT_EQ(sticky_parent,
+            grandchild_constraints.NearestStickyLayerShiftingContainingBlock());
 }
 
 // Verifies that the correct containing-block shifting ancestor is found when
@@ -886,13 +885,13 @@
   EXPECT_TRUE(sticky_outer->StickyConstraints());
 
   ASSERT_TRUE(HasStickyLayer(scrollable_area, sticky_th));
-  auto* th_constraints = sticky_th->StickyConstraints();
+  auto th_constraints = sticky_th->StickyConstraints();
   ASSERT_TRUE(th_constraints);
 
   // The table cell should detect the outer <div> as its containing-block
   // shifting ancestor.
   EXPECT_EQ(sticky_outer,
-            th_constraints->NearestStickyLayerShiftingContainingBlock());
+            th_constraints.NearestStickyLayerShiftingContainingBlock());
 }
 
 // Verifies that the calculated position:sticky offsets are correct when we have
@@ -1254,16 +1253,16 @@
   ASSERT_TRUE(HasStickyLayer(view_scrollable_area, inner_sticky_top));
 
   // innerSticky* should not detect the outer one as any sort of ancestor.
-  auto* inner_constraints_top = inner_sticky_top->StickyConstraints();
+  auto inner_constraints_top = inner_sticky_top->StickyConstraints();
   ASSERT_TRUE(inner_constraints_top);
-  EXPECT_FALSE(inner_constraints_top->NearestStickyLayerShiftingStickyBox());
+  EXPECT_FALSE(inner_constraints_top.NearestStickyLayerShiftingStickyBox());
   EXPECT_FALSE(
-      inner_constraints_top->NearestStickyLayerShiftingContainingBlock());
-  auto* inner_constraints_bottom = inner_sticky_bottom->StickyConstraints();
+      inner_constraints_top.NearestStickyLayerShiftingContainingBlock());
+  auto inner_constraints_bottom = inner_sticky_bottom->StickyConstraints();
   ASSERT_TRUE(inner_constraints_bottom);
-  EXPECT_FALSE(inner_constraints_bottom->NearestStickyLayerShiftingStickyBox());
+  EXPECT_FALSE(inner_constraints_bottom.NearestStickyLayerShiftingStickyBox());
   EXPECT_FALSE(
-      inner_constraints_bottom->NearestStickyLayerShiftingContainingBlock());
+      inner_constraints_bottom.NearestStickyLayerShiftingContainingBlock());
 
   // Scroll the scroller down.
   scroller_scrollable_area->ScrollToAbsolutePositionForTest(
@@ -1520,10 +1519,10 @@
   )HTML");
 
   auto* sticky = GetLayoutBoxByElementId("sticky");
-  auto* constraints = sticky->StickyConstraints();
+  auto constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_EQ(&GetLayoutView(),
-            &constraints->ContainingScrollContainerLayer()->GetLayoutObject());
+            &constraints.ContainingScrollContainerLayer()->GetLayoutObject());
 
   GetDocument().body()->setAttribute(html_names::kStyleAttr,
                                      AtomicString("overflow: hidden"));
@@ -1531,14 +1530,14 @@
   constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_EQ(GetDocument().body()->GetLayoutObject(),
-            &constraints->ContainingScrollContainerLayer()->GetLayoutObject());
+            &constraints.ContainingScrollContainerLayer()->GetLayoutObject());
 
   GetDocument().body()->setAttribute(html_names::kStyleAttr, g_empty_atom);
   UpdateAllLifecyclePhasesForTest();
   constraints = sticky->StickyConstraints();
   ASSERT_TRUE(constraints);
   EXPECT_EQ(&GetLayoutView(),
-            &constraints->ContainingScrollContainerLayer()->GetLayoutObject());
+            &constraints.ContainingScrollContainerLayer()->GetLayoutObject());
 }
 
 TEST_P(LayoutBoxModelObjectTest, RemoveStickyUnderContain) {
@@ -1646,10 +1645,10 @@
 
   ASSERT_TRUE(body->StickyConstraints());
   ASSERT_TRUE(container->StickyConstraints());
-  auto* child_constraints = child->StickyConstraints();
+  auto child_constraints = child->StickyConstraints();
   ASSERT_TRUE(child_constraints);
   EXPECT_EQ(container,
-            child_constraints->NearestStickyLayerShiftingContainingBlock());
+            child_constraints.NearestStickyLayerShiftingContainingBlock());
 
   GetLayoutView().GetScrollableArea()->ScrollToAbsolutePositionForTest(
       gfx::PointF(0, 50));
@@ -1664,7 +1663,7 @@
   child_constraints = child->StickyConstraints();
   ASSERT_TRUE(child_constraints);
   EXPECT_EQ(body,
-            child_constraints->NearestStickyLayerShiftingContainingBlock());
+            child_constraints.NearestStickyLayerShiftingContainingBlock());
 
   // This should not crash.
   GetLayoutView().GetScrollableArea()->ScrollToAbsolutePositionForTest(
diff --git a/third_party/blink/renderer/core/layout/physical_fragment_test.cc b/third_party/blink/renderer/core/layout/physical_fragment_test.cc
index 6f10fc27..b402fd2 100644
--- a/third_party/blink/renderer/core/layout/physical_fragment_test.cc
+++ b/third_party/blink/renderer/core/layout/physical_fragment_test.cc
@@ -111,11 +111,11 @@
 
   ExpectStickyDescendant(
       "inner", "sticky-y",
-      {.consumed = kPhysicalAxesVertical, .pending = kPhysicalAxesNone});
+      {.consumed = kPhysicalAxesBoth, .pending = kPhysicalAxesNone});
 
   ExpectStickyDescendant(
       "inner", "sticky-x",
-      {.consumed = kPhysicalAxesHorizontal, .pending = kPhysicalAxesNone});
+      {.consumed = kPhysicalAxesBoth, .pending = kPhysicalAxesNone});
 
   ExpectStickyDescendant(
       "inner", "sticky-both",
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc
index 3665a78..7edf889 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc
@@ -6,6 +6,7 @@
 
 #include "third_party/blink/renderer/core/layout/block_node.h"
 #include "third_party/blink/renderer/core/layout/constraint_space_builder.h"
+#include "third_party/blink/renderer/core/layout/geometry/axis.h"
 #include "third_party/blink/renderer/core/layout/hit_test_result.h"
 #include "third_party/blink/renderer/core/layout/layout_result.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_layout_info.h"
@@ -152,7 +153,7 @@
   for (const auto& item :
        content_result->GetPhysicalFragment().StickyDescendants()) {
     if (auto* pending = item.GetIfPending()) {
-      pending->SetStickyConstraints(nullptr);
+      pending->ClearStickyConstraints(kPhysicalAxesBoth);
     }
   }
 
diff --git a/third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.cc b/third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.cc
index ff123dd..cc4d89a 100644
--- a/third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.cc
+++ b/third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.cc
@@ -13,17 +13,12 @@
 namespace blink {
 
 namespace {
-BoxEdge RectToLayoutUnitSegment(PhysicalAxis axis, const PhysicalRect& rect) {
+BoxEdge RectToBoxEdge(PhysicalAxis axis, const PhysicalRect& rect) {
   return axis == PhysicalAxis::kHorizontal ? BoxEdge(rect.X(), rect.Width())
                                            : BoxEdge(rect.Y(), rect.Height());
 }
 }  // namespace
 
-StickyPositionScrollingConstraints::StickyPositionScrollingConstraints(
-    PerAxisData* x_data,
-    PerAxisData* y_data)
-    : x_data_(x_data), y_data_(y_data) {}
-
 StickyPositionScrollingConstraints::PerAxisData::PerAxisData(
     PhysicalAxis axis,
     const PhysicalRect& containing_block,
@@ -39,10 +34,10 @@
       min_inset(min_inset),
       max_inset(max_inset),
       scroll_container_relative_containing_block_range(
-          RectToLayoutUnitSegment(axis, containing_block)),
+          RectToBoxEdge(axis, containing_block)),
       scroll_container_relative_sticky_box_range(
-          RectToLayoutUnitSegment(axis, sticky_box)),
-      constraining_range(RectToLayoutUnitSegment(axis, constraining)),
+          RectToBoxEdge(axis, sticky_box)),
+      constraining_range(RectToBoxEdge(axis, constraining)),
       nearest_sticky_layer_shifting_sticky_box(
           nearest_sticky_layer_shifting_sticky_box),
       nearest_sticky_layer_shifting_containing_block(
@@ -131,12 +126,13 @@
 }
 
 void StickyPositionScrollingConstraints::ComputeStickyOffset(
-    const gfx::PointF& scroll_position) {
-  if (x_data_) {
+    const gfx::PointF& scroll_position,
+    PhysicalAxes scroll_axes) {
+  if (x_data_ && (scroll_axes & kPhysicalAxesHorizontal)) {
     x_data_->ComputeOffset(scroll_position.x());
   }
 
-  if (y_data_) {
+  if (y_data_ && (scroll_axes & kPhysicalAxesVertical)) {
     y_data_->ComputeOffset(scroll_position.y());
   }
 }
@@ -176,11 +172,11 @@
   if (!nearest_sticky_layer_shifting_sticky_box) {
     return LayoutUnit();
   }
-  const auto* constraints =
+  const auto constraints =
       nearest_sticky_layer_shifting_sticky_box->StickyConstraints();
   DCHECK(constraints);
 
-  if (const auto* ancestor_data = constraints->AxisData(axis)) {
+  if (const auto* ancestor_data = constraints.AxisData(axis)) {
     return ancestor_data->total_sticky_box_sticky_offset;
   }
   return LayoutUnit();
@@ -192,40 +188,18 @@
   if (!nearest_sticky_layer_shifting_containing_block) {
     return LayoutUnit();
   }
-  const auto* constraints =
+  const auto constraints =
       nearest_sticky_layer_shifting_containing_block->StickyConstraints();
   DCHECK(constraints);
 
-  if (const auto* ancestor_data = constraints->AxisData(axis)) {
+  if (const auto* ancestor_data = constraints.AxisData(axis)) {
     return ancestor_data->total_containing_block_sticky_offset;
   }
   return LayoutUnit();
 }
 
-void StickyPositionScrollingConstraints::Trace(Visitor* visitor) const {
-  visitor->Trace(x_data_);
-  visitor->Trace(y_data_);
-}
-
 const StickyPositionScrollingConstraints::PerAxisData*
 StickyPositionScrollingConstraints::PreferredAxisData() const {
-  // Prefer the axis that has a valid scroll container layer.
-  bool x_valid = x_data_ && x_data_->containing_scroll_container_layer;
-  bool y_valid = y_data_ && y_data_->containing_scroll_container_layer;
-  if (x_valid != y_valid) {
-    return x_valid ? x_data_ : y_data_;
-  }
-
-  // If exactly one axis is actually constrained, prefer it.
-  const bool x_has_insets =
-      x_data_ && (x_data_->min_inset || x_data_->max_inset);
-  const bool y_has_insets =
-      y_data_ && (y_data_->min_inset || y_data_->max_inset);
-  if (x_has_insets != y_has_insets) {
-    return x_has_insets ? x_data_ : y_data_;
-  }
-
-  // Otherwise default to horizontal-first determinism.
   return x_data_ ? x_data_ : y_data_;
 }
 
diff --git a/third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.h b/third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.h
index 16e1b3d1..4160c576 100644
--- a/third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.h
+++ b/third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.h
@@ -74,15 +74,10 @@
 // already being shifted by its ancestor. To correctly handle such situations we
 // apply more complicated logic which is explained in the implementation of
 // |ComputeStickyOffset|.
-struct CORE_EXPORT StickyPositionScrollingConstraints final
-    : public GarbageCollected<StickyPositionScrollingConstraints> {
- public:
-  // Computes the sticky offset for a given scroll position of the containing
-  // scroll container. When the scroll position changed in a ScrollableArea,
-  // this method must be called for all affected sticky objects in pre-tree
-  // order.
-  void ComputeStickyOffset(const gfx::PointF& scroll_position);
+struct CORE_EXPORT StickyPositionScrollingConstraints final {
+  STACK_ALLOCATED();
 
+ public:
   // The containing block rect and sticky box rect are the basic components
   // for calculating the sticky offset to apply after a scroll. Consider the
   // following setup:
@@ -198,13 +193,21 @@
     LayoutUnit AncestorContainingBlockOffset() const;
   };
 
-  StickyPositionScrollingConstraints(PerAxisData* x_data, PerAxisData* y_data);
+  StickyPositionScrollingConstraints() = default;
+  StickyPositionScrollingConstraints(PerAxisData* x_data, PerAxisData* y_data)
+      : x_data_(x_data), y_data_(y_data) {}
+
+  explicit operator bool() const { return x_data_ || y_data_; }
+
+  // Computes the sticky offset for a given scroll position of the containing
+  // scroll container. When the scroll position changed in a ScrollableArea,
+  // this method must be called for all affected sticky objects in pre-tree
+  // order.
+  void ComputeStickyOffset(const gfx::PointF& scroll_position,
+                           PhysicalAxes scroll_axes);
 
   const PerAxisData* AxisData(PhysicalAxis axis) const {
-    return (axis == PhysicalAxis::kHorizontal) ? x_data_.Get() : y_data_.Get();
-  }
-  PerAxisData* AxisData(PhysicalAxis axis) {
-    return (axis == PhysicalAxis::kHorizontal) ? x_data_.Get() : y_data_.Get();
+    return (axis == PhysicalAxis::kHorizontal) ? x_data_ : y_data_;
   }
 
   bool HasScrollDependentOffset() const;
@@ -269,23 +272,28 @@
     return nullptr;
   }
 
-  void Trace(Visitor* visitor) const;
-
  private:
   const PerAxisData* PreferredAxisData() const;
 
   // Safely builds a 2D rect from 1D ranges, falling back to 0 if an axis is
   // missing.
   PhysicalRect BuildRect(const BoxEdge* x, const BoxEdge* y) const {
-    DCHECK(x && y)
-        << "2D geometric reconstruction requires both axes to be populated.";
     return PhysicalRect(x ? x->offset : LayoutUnit(),
                         y ? y->offset : LayoutUnit(),
                         x ? x->size : LayoutUnit(), y ? y->size : LayoutUnit());
   }
 
-  Member<PerAxisData> x_data_;
-  Member<PerAxisData> y_data_;
+  PerAxisData* x_data_ = nullptr;
+  PerAxisData* y_data_ = nullptr;
+};
+
+// Per-axis sticky constraints update payload.
+struct CORE_EXPORT StickyConstraintsData {
+  STACK_ALLOCATED();
+
+ public:
+  StickyPositionScrollingConstraints::PerAxisData* x_data = nullptr;
+  StickyPositionScrollingConstraints::PerAxisData* y_data = nullptr;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc b/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc
index 3bafcd7..243ecc3b 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc
@@ -286,8 +286,22 @@
   // We check for |HasOverflow| instead of |ScrollsOverflow| to ensure sticky
   // position elements are composited under overflow: hidden, which can still
   // have smooth scroll animations.
-  if (const auto* constraints = layer.GetLayoutObject().StickyConstraints()) {
-    if (constraints->HasScrollDependentOffset()) {
+  auto constraints = layer.GetLayoutObject().StickyConstraints();
+  if (constraints.HasScrollDependentOffset()) {
+    const auto* x_data = constraints.AxisData(PhysicalAxis::kHorizontal);
+    const auto* y_data = constraints.AxisData(PhysicalAxis::kVertical);
+    const auto* x_layer =
+        x_data ? x_data->containing_scroll_container_layer.Get() : nullptr;
+    const auto* y_layer =
+        y_data ? y_data->containing_scroll_container_layer.Get() : nullptr;
+
+    bool has_multiple_scrollers = x_layer && y_layer && x_layer != y_layer;
+    CHECK(RuntimeEnabledFeatures::SingleAxisScrollContainersEnabled() ||
+          !has_multiple_scrollers);
+
+    if (!has_multiple_scrollers) {
+      // TODO(crbug.com/481019005): Implement compositor support for multiple
+      // scroll container parents. Fall back to main-thread scrolling for now.
       reasons |= CompositingReason::kStickyPosition;
     }
   }
@@ -347,7 +361,8 @@
   CompositingReasons reasons = CompositingReason::kNone;
 
   auto* element = DynamicTo<Element>(object.GetNode());
-  if (element && RuntimeEnabledFeatures::CanvasDrawElementEnabled()) {
+  if (element && IsA<LayoutBox>(object) &&
+      RuntimeEnabledFeatures::CanvasDrawElementEnabled()) {
     if (element->IsInCanvasSubtree()) [[unlikely]] {
       auto* canvas_parent =
           DynamicTo<HTMLCanvasElement>(element->parentElement());
diff --git a/third_party/blink/renderer/core/paint/fragment_data.cc b/third_party/blink/renderer/core/paint/fragment_data.cc
index 15ff311..d4124887 100644
--- a/third_party/blink/renderer/core/paint/fragment_data.cc
+++ b/third_party/blink/renderer/core/paint/fragment_data.cc
@@ -25,14 +25,16 @@
 void FragmentData::RareData::SetLayer(PaintLayer* new_layer) {
   if (layer && layer != new_layer) {
     layer->Destroy();
-    sticky_constraints = nullptr;
+    x_sticky_constraints = nullptr;
+    y_sticky_constraints = nullptr;
   }
   layer = new_layer;
 }
 
 void FragmentData::RareData::Trace(Visitor* visitor) const {
   visitor->Trace(layer);
-  visitor->Trace(sticky_constraints);
+  visitor->Trace(x_sticky_constraints);
+  visitor->Trace(y_sticky_constraints);
   visitor->Trace(additional_fragments);
   visitor->Trace(paint_properties);
   visitor->Trace(local_border_box_properties);
diff --git a/third_party/blink/renderer/core/paint/fragment_data.h b/third_party/blink/renderer/core/paint/fragment_data.h
index 7a2ec831..40643d4 100644
--- a/third_party/blink/renderer/core/paint/fragment_data.h
+++ b/third_party/blink/renderer/core/paint/fragment_data.h
@@ -8,7 +8,9 @@
 #include <optional>
 
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/layout/geometry/axis.h"
 #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
+#include "third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.h"
 #include "third_party/blink/renderer/core/paint/object_paint_properties.h"
 #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
 #include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h"
@@ -49,15 +51,54 @@
   }
   void SetLayer(PaintLayer*);
 
-  StickyPositionScrollingConstraints* StickyConstraints() const {
+  StickyPositionScrollingConstraints StickyConstraints() const {
     AssertIsFirst();
-    return rare_data_ ? rare_data_->sticky_constraints.Get() : nullptr;
+    return rare_data_ ? StickyPositionScrollingConstraints(
+                            rare_data_->x_sticky_constraints.Get(),
+                            rare_data_->y_sticky_constraints.Get())
+                      : StickyPositionScrollingConstraints();
   }
-  void SetStickyConstraints(StickyPositionScrollingConstraints* constraints) {
+
+  bool HasStickyConstraints() const {
     AssertIsFirst();
-    if (!rare_data_ && !constraints)
-      return;
-    EnsureRareData().sticky_constraints = constraints;
+    return rare_data_ && (rare_data_->x_sticky_constraints ||
+                          rare_data_->y_sticky_constraints);
+  }
+
+  bool SetStickyConstraints(StickyConstraintsData constraints) {
+    AssertIsFirst();
+    DCHECK(constraints.x_data || constraints.y_data);
+    bool has_changed = false;
+    auto& rare_data = EnsureRareData();
+    if (constraints.x_data) {
+      has_changed = true;
+      rare_data.x_sticky_constraints = constraints.x_data;
+    }
+    if (constraints.y_data) {
+      has_changed = true;
+      rare_data.y_sticky_constraints = constraints.y_data;
+    }
+    return has_changed;
+  }
+
+  bool ClearStickyConstraints(PhysicalAxes axes_to_clear) {
+    AssertIsFirst();
+
+    bool has_changed = false;
+    if (!rare_data_) {
+      return has_changed;
+    }
+    if ((axes_to_clear & kPhysicalAxesHorizontal) &&
+        rare_data_->x_sticky_constraints) {
+      has_changed = true;
+      rare_data_->x_sticky_constraints = nullptr;
+    }
+    if ((axes_to_clear & kPhysicalAxesVertical) &&
+        rare_data_->y_sticky_constraints) {
+      has_changed = true;
+      rare_data_->y_sticky_constraints = nullptr;
+    }
+    return has_changed;
   }
 
   // Holds references to the paint property nodes created by this object.
@@ -178,7 +219,10 @@
     // avoid separate data structure for them. They are only to be accessed in
     // the first fragment.
     Member<PaintLayer> layer;
-    Member<StickyPositionScrollingConstraints> sticky_constraints;
+    Member<StickyPositionScrollingConstraints::PerAxisData>
+        x_sticky_constraints;
+    Member<StickyPositionScrollingConstraints::PerAxisData>
+        y_sticky_constraints;
     HeapVector<Member<FragmentData>> additional_fragments;
 
     // Fragment specific data.
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index 6add3a3..ebeb6b6 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -2413,10 +2413,12 @@
   for (const auto& fragment : GetLayoutBox()->PhysicalFragments()) {
     for (const auto& item : fragment.StickyDescendants()) {
       if (auto* sticky_descendant = item.GetIfConsumed()) {
-        auto* constraints =
-            sticky_descendant->ComputeStickyPositionConstraints();
-        constraints->ComputeStickyOffset(ScrollPosition());
-        sticky_descendant->SetStickyConstraints(constraints);
+        StickyConstraintsData data =
+            sticky_descendant->ComputeStickyPositionConstraints(
+                *Layer(), item.ConsumedAxes());
+        sticky_descendant->SetStickyConstraints(data);
+        sticky_descendant->StickyConstraints().ComputeStickyOffset(
+            ScrollPosition(), item.ConsumedAxes());
       }
     }
   }
@@ -2451,8 +2453,8 @@
       if (auto* sticky_descendant = item.GetIfConsumed()) {
         sticky_descendant->SetNeedsPaintPropertyUpdate();
         DCHECK(sticky_descendant->StickyConstraints());
-        sticky_descendant->StickyConstraints()->ComputeStickyOffset(
-            ScrollPosition());
+        sticky_descendant->StickyConstraints().ComputeStickyOffset(
+            ScrollPosition(), item.ConsumedAxes());
       }
     }
   }
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index d7c4110..becbdcb 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -539,10 +539,10 @@
 }
 
 static bool NeedsStickyTranslation(const LayoutObject& object) {
-  if (!object.IsBoxModelObject())
-    return false;
-
-  return To<LayoutBoxModelObject>(object).StickyConstraints();
+  if (const auto* box_model = DynamicTo<LayoutBoxModelObject>(object)) {
+    return box_model->HasStickyConstraints();
+  }
+  return false;
 }
 
 static bool NeedsAnchorPositionScrollTranslation(const LayoutObject& object) {
@@ -853,10 +853,10 @@
           context_.should_flatten_inherited_transform;
 
       if (state.direct_compositing_reasons) {
-        const auto* layout_constraint = box_model.StickyConstraints();
+        const auto layout_constraint = box_model.StickyConstraints();
         DCHECK(layout_constraint);
         const auto* scroll_container_properties =
-            layout_constraint->ContainingScrollContainerLayer()
+            layout_constraint.ContainingScrollContainerLayer()
                 ->GetLayoutObject()
                 .FirstFragment()
                 .PaintProperties();
@@ -871,29 +871,29 @@
         if (scroll_container_scrolls) {
           auto constraint = std::make_unique<CompositorStickyConstraint>();
           constraint->is_anchored_left =
-              layout_constraint->LeftInset().has_value();
+              layout_constraint.LeftInset().has_value();
           constraint->is_anchored_right =
-              layout_constraint->RightInset().has_value();
+              layout_constraint.RightInset().has_value();
           constraint->is_anchored_top =
-              layout_constraint->TopInset().has_value();
+              layout_constraint.TopInset().has_value();
           constraint->is_anchored_bottom =
-              layout_constraint->BottomInset().has_value();
+              layout_constraint.BottomInset().has_value();
 
           constraint->left_offset =
-              layout_constraint->LeftInset().value_or(LayoutUnit()).ToFloat();
+              layout_constraint.LeftInset().value_or(LayoutUnit()).ToFloat();
           constraint->right_offset =
-              layout_constraint->RightInset().value_or(LayoutUnit()).ToFloat();
+              layout_constraint.RightInset().value_or(LayoutUnit()).ToFloat();
           constraint->top_offset =
-              layout_constraint->TopInset().value_or(LayoutUnit()).ToFloat();
+              layout_constraint.TopInset().value_or(LayoutUnit()).ToFloat();
           constraint->bottom_offset =
-              layout_constraint->BottomInset().value_or(LayoutUnit()).ToFloat();
+              layout_constraint.BottomInset().value_or(LayoutUnit()).ToFloat();
           constraint->constraint_box_rect =
-              gfx::RectF(layout_constraint->ConstrainingRect());
+              gfx::RectF(layout_constraint.ConstrainingRect());
           constraint->scroll_container_relative_sticky_box_rect = gfx::RectF(
-              layout_constraint->ScrollContainerRelativeStickyBoxRect());
+              layout_constraint.ScrollContainerRelativeStickyBoxRect());
           constraint->scroll_container_relative_containing_block_rect =
               gfx::RectF(layout_constraint
-                             ->ScrollContainerRelativeContainingBlockRect());
+                             .ScrollContainerRelativeContainingBlockRect());
 
           constraint->pixel_snap_offset = gfx::Vector2dF(pixel_snap_offset);
           // gfx::Vector2dF rounds differently than PhysicalOffset at
@@ -916,7 +916,7 @@
               gfx::Vector2dF(adjustment_left, adjustment_top);
 
           if (const LayoutBoxModelObject* sticky_box_shifting_ancestor =
-                  layout_constraint->NearestStickyLayerShiftingStickyBox()) {
+                  layout_constraint.NearestStickyLayerShiftingStickyBox()) {
             constraint->nearest_element_shifting_sticky_box =
                 CompositorElementIdFromUniqueObjectId(
                     sticky_box_shifting_ancestor->UniqueId(),
@@ -924,7 +924,7 @@
           }
           if (const LayoutBoxModelObject* containing_block_shifting_ancestor =
                   layout_constraint
-                      ->NearestStickyLayerShiftingContainingBlock()) {
+                      .NearestStickyLayerShiftingContainingBlock()) {
             constraint->nearest_element_shifting_containing_block =
                 CompositorElementIdFromUniqueObjectId(
                     containing_block_shifting_ancestor->UniqueId(),
@@ -1944,8 +1944,13 @@
 
         if (state.direct_compositing_reasons &
             CompositingReason::kCanvasChild) {
-          state.canvas_child_id = CompositorElementIdFromDOMNodeId(
-              object_.GetNode()->GetDomNodeId());
+          CHECK(IsA<LayoutBox>(object_));
+          auto& canvas_fragment = object_.Parent()->FirstFragment();
+          state.canvas_child_state = {
+              object_.GetNode()->GetDomNodeId(),
+              gfx::SizeF(DynamicTo<LayoutBox>(object_)->StitchedSize()),
+              object_.StyleRef().EffectiveZoom(),
+              canvas_fragment.ContentsEffect(), canvas_fragment.ContentsClip()};
         }
       } else {
         // The effect node CompositorElementId is used to uniquely identify
diff --git a/third_party/blink/renderer/core/script_tools/model_context.cc b/third_party/blink/renderer/core/script_tools/model_context.cc
index 76e506c2..66bfce9 100644
--- a/third_party/blink/renderer/core/script_tools/model_context.cc
+++ b/third_party/blink/renderer/core/script_tools/model_context.cc
@@ -163,7 +163,7 @@
 void ModelContext::ForEachScriptTool(
     base::FunctionRef<void(const mojom::blink::ScriptTool&)> func) const {
   for (const ToolData* tool_data : ListTools()) {
-    func(*tool_data->script_tool_);
+    func(tool_data->ScriptTool());
   }
 }
 
@@ -193,11 +193,11 @@
     WebDocument::ScriptToolDeclaration* tool_declaration) const {
   auto it = tool_map_.find(name);
   if (it != tool_map_.end()) {
-    tool_declaration->description = it->value->script_tool_->description;
-    tool_declaration->input_schema = it->value->script_tool_->input_schema;
-    if (it->value->script_tool_->annotations) {
-      tool_declaration->read_only =
-          it->value->script_tool_->annotations->read_only;
+    const mojom::blink::ScriptTool& script_tool = it->value->ScriptTool();
+    tool_declaration->description = script_tool.description;
+    tool_declaration->input_schema = script_tool.input_schema;
+    if (script_tool.annotations) {
+      tool_declaration->read_only = script_tool.annotations->read_only;
     }
   }
 }
@@ -238,15 +238,14 @@
   }
 
   std::optional<uint32_t> execution_id;
-  if (it->value->v8_tool_function_) {
-    execution_id =
-        ExecuteV8Tool(it->value->v8_tool_function_, name, input_arguments,
-                      signal, std::move(tool_executed_cb));
+  if (V8ToolFunction* v8_tool_function = it->value->GetV8ToolFunction()) {
+    execution_id = ExecuteV8Tool(v8_tool_function, name, input_arguments,
+                                 signal, std::move(tool_executed_cb));
   } else {
     // TODO(479598776): Add support for tracking execution of
     // declarative tools, so that they can be cancelled.
     // TODO(481899636): Add signal support for declarative tools.
-    ExecuteDeclarativeTool(it->value->declarative_tool_, input_arguments,
+    ExecuteDeclarativeTool(it->value->DeclarativeTool(), input_arguments,
                            std::move(tool_executed_cb));
   }
 
@@ -450,8 +449,6 @@
     }
   }
 
-  auto* tool_data = MakeGarbageCollected<ToolData>();
-
   auto script_tool = mojom::blink::ScriptTool::New();
   script_tool->name = params->name();
   script_tool->description = params->description();
@@ -462,28 +459,28 @@
     script_tool->annotations->read_only = params->annotations()->readOnlyHint();
   }
 
-  tool_data->script_tool_ = std::move(script_tool);
-  tool_data->v8_tool_function_ = params->execute();
-  tool_data->source_location_ =
-      CaptureSourceLocation(ExecutionContext::From(script_state));
+  auto* tool_data = MakeGarbageCollected<ToolData>(
+      base::PassKey<ModelContext>(), std::move(script_tool),
+      /*v8_tool_function=*/params->execute(),
+      CaptureSourceLocation(ExecutionContext::From(script_state)));
 
-  tool_map_.insert(params->name(), std::move(tool_data));
+  tool_map_.insert(params->name(), tool_data);
   OnToolsChanged();
   UseCounter::Count(document_, WebFeature::kModelContextRegisterTool);
   return true;
 }
 
-void ModelContext::RegisterDeclarativeTool(String name,
-                                           String description,
-                                           DeclarativeWebMCPTool* tool) {
+void ModelContext::RegisterDeclarativeTool(
+    String name,
+    String description,
+    DeclarativeWebMCPTool* declarative_tool) {
   auto script_tool = mojom::blink::ScriptTool::New();
-  auto* tool_data = MakeGarbageCollected<ToolData>();
   script_tool->name = name;
   script_tool->description = description;
   script_tool->input_schema = "{}";  // For now
-  tool_data->script_tool_ = std::move(script_tool);
-  tool_data->declarative_tool_ = tool;
 
+  auto* tool_data = MakeGarbageCollected<ToolData>(
+      base::PassKey<ModelContext>(), std::move(script_tool), declarative_tool);
   tool_map_.insert(name, std::move(tool_data));
   OnToolsChanged();
   UseCounter::Count(document_,
@@ -531,11 +528,7 @@
     CHECK(tool_data);
     // Always update the input schema of declarative tools,
     // since the DOM might have changed.
-    if (DeclarativeWebMCPTool* declarative_tool =
-            tool_data->declarative_tool_) {
-      tool_data->script_tool_->input_schema =
-          declarative_tool->ComputeInputSchema();
-    }
+    tool_data->RefreshDeclarativeInputSchema();
     tools.push_back(tool_data);
   }
 
@@ -566,10 +559,20 @@
   return source_location_;
 }
 
+Element* ModelContext::ToolData::BackingFormElement() const {
+  return declarative_tool_ ? declarative_tool_->FormElement() : nullptr;
+}
+
 void ModelContext::ToolData::Trace(Visitor* visitor) const {
   visitor->Trace(v8_tool_function_);
   visitor->Trace(declarative_tool_);
   visitor->Trace(source_location_);
 }
 
+void ModelContext::ToolData::RefreshDeclarativeInputSchema() {
+  if (declarative_tool_) {
+    script_tool_->input_schema = declarative_tool_->ComputeInputSchema();
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/script_tools/model_context.h b/third_party/blink/renderer/core/script_tools/model_context.h
index 51f706b7..9272fc94 100644
--- a/third_party/blink/renderer/core/script_tools/model_context.h
+++ b/third_party/blink/renderer/core/script_tools/model_context.h
@@ -9,6 +9,7 @@
 
 #include "base/functional/callback.h"
 #include "base/functional/callback_forward.h"
+#include "base/types/pass_key.h"
 #include "third_party/blink/public/mojom/content_extraction/script_tools.mojom-blink.h"
 #include "third_party/blink/public/web/web_document.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_model_context.h"
@@ -24,6 +25,7 @@
 namespace blink {
 
 class AbortSignal;
+class Element;
 class SourceLocation;
 
 class DeclarativeWebMCPTool : public GarbageCollectedMixin {
@@ -40,6 +42,9 @@
 
   // Returns the input json-schema associated with the tool.
   virtual String ComputeInputSchema() = 0;
+
+  // The <form> backing this declarative tool.
+  virtual Element* FormElement() const = 0;
 };
 
 class CORE_EXPORT ModelContext : public ScriptWrappable {
@@ -93,17 +98,44 @@
 
   class CORE_EXPORT ToolData : public GarbageCollected<ToolData> {
    public:
+    // Creates a JS-backed tool.
+    ToolData(base::PassKey<ModelContext>,
+             mojo::StructPtr<mojom::blink::ScriptTool> script_tool,
+             V8ToolFunction* v8_tool_function,
+             SourceLocation* source_location)
+        : script_tool_(std::move(script_tool)),
+          v8_tool_function_(v8_tool_function),
+          source_location_(source_location) {}
+
+    // Creates a declarative (<form>-backed) tool.
+    ToolData(base::PassKey<ModelContext>,
+             mojo::StructPtr<mojom::blink::ScriptTool> script_tool,
+             DeclarativeWebMCPTool* declarative_tool)
+        : script_tool_(std::move(script_tool)),
+          declarative_tool_(declarative_tool) {}
+
     const String& Name() const;
 
+    const mojom::blink::ScriptTool& ScriptTool() const { return *script_tool_; }
+
     // If this is a JS-provided tool, returns the source location
     // of the call to registerTool(). Otherwise, returns nullptr.
     SourceLocation* GetSourceLocation() const;
 
+    // If this is a declarative tool, returns the <form> element
+    // that provided this tool. Otherwise, returns nullptr.
+    Element* BackingFormElement() const;
+
     void Trace(Visitor* visitor) const;
 
    private:
     friend class ModelContext;
 
+    V8ToolFunction* GetV8ToolFunction() const { return v8_tool_function_; }
+    DeclarativeWebMCPTool* DeclarativeTool() const { return declarative_tool_; }
+
+    void RefreshDeclarativeInputSchema();
+
     mojo::StructPtr<mojom::blink::ScriptTool> script_tool_;
     // A JS-provided MCP tool:
     Member<V8ToolFunction> v8_tool_function_;
diff --git a/third_party/blink/renderer/core/script_tools/model_context_test.cc b/third_party/blink/renderer/core/script_tools/model_context_test.cc
index e5e3cdf..5926c595 100644
--- a/third_party/blink/renderer/core/script_tools/model_context_test.cc
+++ b/third_party/blink/renderer/core/script_tools/model_context_test.cc
@@ -1053,6 +1053,7 @@
                        done_callback) override {}
 
   String ComputeInputSchema() override { return "{}"; }
+  Element* FormElement() const override { return nullptr; }
   void Trace(Visitor* visitor) const override {}
 };
 
@@ -1171,4 +1172,38 @@
   EXPECT_EQ(8u, tools[1]->GetSourceLocation()->LineNumber());
 }
 
+TEST_F(ModelContextTest, BackingFormElement) {
+  SimRequest main_resource("https://example.com/", "text/html");
+  LoadURL("https://example.com/");
+
+  main_resource.Complete(R"(<!DOCTYPE html>
+    <form
+      id=book-table
+      toolname=book-table
+      tooldescription="Book a table">
+    </form>
+    <form
+      id=leave-feedback
+      toolname=leave-feedback
+      tooldescription="leave-feedback">
+    </form>
+  )");
+
+  auto* model_context =
+      ModelContextSupplement::modelContext(*Window().navigator());
+  ASSERT_TRUE(model_context);
+
+  HeapVector<Member<const ModelContext::ToolData>> tools =
+      model_context->ListTools();
+  ASSERT_EQ(2u, tools.size());
+
+  EXPECT_EQ("book-table", tools[0]->Name());
+  ASSERT_TRUE(tools[0]->BackingFormElement());
+  EXPECT_EQ("book-table", tools[0]->BackingFormElement()->GetIdAttribute());
+
+  EXPECT_EQ("leave-feedback", tools[1]->Name());
+  ASSERT_TRUE(tools[1]->BackingFormElement());
+  EXPECT_EQ("leave-feedback", tools[1]->BackingFormElement()->GetIdAttribute());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index db036af..16d4166a 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -86,6 +86,7 @@
 #include "third_party/blink/renderer/core/layout/layout_replaced.h"
 #include "third_party/blink/renderer/core/layout/layout_theme.h"
 #include "third_party/blink/renderer/core/layout/map_coordinates_flags.h"
+#include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/scroll/scroll_alignment.h"
 #include "third_party/blink/renderer/core/scroll/scroll_into_view_util.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
@@ -887,29 +888,28 @@
     return nullptr;
   }
 
+  if (!IsDrawElementImageEligible(element, "DrawElementImage",
+                                  exception_state)) {
+    return nullptr;
+  }
+
   TRACE_EVENT0("blink", "DrawElementImage");
 
-  element->GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
-      DocumentUpdateReason::kCanvasDrawElementImage);
-
-  // Element size in physical coordinates.
-  gfx::SizeF box_size;
-  if (element->GetLayoutBox()) {
-    box_size = gfx::SizeF(element->GetLayoutBox()->StitchedSize());
-  }
-  gfx::RectF src_rect(box_size);
-  std::optional<CullRect> cull_rect;
-  if (sx && sy && swidth && sheight) {
-    float dpr = element->ComputedStyleRef().EffectiveZoom();
-    src_rect = gfx::RectF(*sx * dpr, *sy * dpr, *swidth * dpr, *sheight * dpr);
-    cull_rect.emplace(gfx::ToEnclosingRect(src_rect));
-  }
-
-  std::optional<cc::PaintRecord> paint_record = GetElementPaintRecord(
-      element, cull_rect, "drawElementImage()", exception_state);
-  if (!paint_record) {
+  std::optional<CanvasChildPaintRecord> child_paint_record =
+      GetChildPaintRecord(element);
+  if (!child_paint_record) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "No cached paint record for element.");
     return nullptr;
   }
+  float dpr = child_paint_record->scale;
+  gfx::SizeF box_size = child_paint_record->box_size;
+  cc::PaintRecord paint_record = std::move(child_paint_record->record);
+
+  gfx::RectF src_rect(box_size);
+  if (sx && sy && swidth && sheight) {
+    src_rect = gfx::RectF(*sx * dpr, *sy * dpr, *swidth * dpr, *sheight * dpr);
+  }
 
   // The filter needs to be resolved before calling Draw, because it
   // immediately checks IsFilterResolved() and uses a null canvas if not.
@@ -989,7 +989,7 @@
         c->clipRect(SkRect::MakeXYWH(src_rect.x(), src_rect.y(),
                                      src_rect.width(), src_rect.height()));
 
-        c->drawPicture(paint_record.value(),
+        c->drawPicture(std::move(paint_record),
                        // use a save at the beginning of the record to keep
                        // transforms local:
                        true);
diff --git a/third_party/blink/renderer/modules/webaudio/audio_handler.cc b/third_party/blink/renderer/modules/webaudio/audio_handler.cc
index 346a396..e65a4fd 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_handler.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_handler.cc
@@ -564,13 +564,13 @@
   channel_interpretation_ = new_channel_interpretation_;
 }
 
-void AudioHandler::SendLogMessage(const char* const function_name,
+void AudioHandler::SendLogMessage(const String& function_name,
                                   const String& message) {
   WebRtcLogMessage(
-      UNSAFE_TODO(String::Format("[WA]AH::%s %s [type=%s, this=0x%" PRIXPTR "]",
-                                 function_name, message.Utf8().c_str(),
-                                 NodeTypeName().Utf8().c_str(),
-                                 reinterpret_cast<uintptr_t>(this)))
+      String::Format("[WA]AH::%s %s [type=%s, this=0x%" PRIXPTR "]",
+                     function_name.Utf8().c_str(), message.Utf8().c_str(),
+                     NodeTypeName().Utf8().c_str(),
+                     reinterpret_cast<uintptr_t>(this))
           .Utf8());
 }
 
diff --git a/third_party/blink/renderer/modules/webaudio/audio_handler.h b/third_party/blink/renderer/modules/webaudio/audio_handler.h
index 83c53ed3..79f6e3b4 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_handler.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_handler.h
@@ -296,7 +296,7 @@
   void SetNodeType(NodeType);
 
   // https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/media/capture/README.md#logs
-  void SendLogMessage(const char* const function_name, const String& message);
+  void SendLogMessage(const String& function_name, const String& message);
 
   bool is_initialized_ = false;
   NodeType node_type_ = NodeType::kNodeTypeUnknown;
diff --git a/third_party/blink/renderer/modules/webaudio/audio_node.cc b/third_party/blink/renderer/modules/webaudio/audio_node.cc
index 79b1781..156ead4 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_node.cc
@@ -621,10 +621,10 @@
   DCHECK_EQ(number_of_outputs, connected_params_.size());
 }
 
-void AudioNode::SendLogMessage(const char* const function_name,
+void AudioNode::SendLogMessage(const String& function_name,
                                const String& message) {
-  WebRtcLogMessage(UNSAFE_TODO(String::Format("[WA]AN::%s %s", function_name,
-                                              message.Utf8().c_str()))
+  WebRtcLogMessage(String::Format("[WA]AN::%s %s", function_name.Utf8().c_str(),
+                                  message.Utf8().c_str())
                        .Utf8());
 }
 
diff --git a/third_party/blink/renderer/modules/webaudio/audio_node.h b/third_party/blink/renderer/modules/webaudio/audio_node.h
index 4e6d61a5..ae63d6b3 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_node.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_node.h
@@ -140,7 +140,7 @@
   virtual void ConnectToDestinationReady() {}
 
   // https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/media/capture/README.md#logs
-  void SendLogMessage(const char* const function_name, const String& message);
+  void SendLogMessage(const String& function_name, const String& message);
 
   Member<BaseAudioContext> context_;
   scoped_refptr<DeferredTaskHandler> deferred_task_handler_;
diff --git a/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_handler.cc b/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_handler.cc
index 7b4f363..103071a8 100644
--- a/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_handler.cc
+++ b/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_handler.cc
@@ -180,13 +180,13 @@
 }
 
 void MediaStreamAudioDestinationHandler::SendLogMessage(
-    const char* const function_name,
+    const String& function_name,
     const String& message) {
-  WebRtcLogMessage(
-      UNSAFE_TODO(String::Format("[WA]MSADH::%s %s [this=0x%" PRIXPTR "]",
-                                 function_name, message.Utf8().c_str(),
-                                 reinterpret_cast<uintptr_t>(this)))
-          .Utf8());
+  WebRtcLogMessage(String::Format("[WA]MSADH::%s %s [this=0x%" PRIXPTR "]",
+                                  function_name.Utf8().c_str(),
+                                  message.Utf8().c_str(),
+                                  reinterpret_cast<uintptr_t>(this))
+                       .Utf8());
 }
 
 void MediaStreamAudioDestinationHandler::SetConsumer(
diff --git a/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_handler.h b/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_handler.h
index f4172b7..6d16a3a 100644
--- a/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_handler.h
+++ b/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_handler.h
@@ -73,7 +73,7 @@
   void ConsumeAudio(const AudioBus* const bus, int number_of_frames);
 
   // https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/media/capture/README.md#logs
-  void SendLogMessage(const char* const function_name, const String& message);
+  void SendLogMessage(const String& function_name, const String& message);
 
   base::Lock consumer_lock_;
   // `destination_consumer_` is owned by the node's MediaStreamSource.
diff --git a/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_node.cc b/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_node.cc
index 2acb017..4e55131 100644
--- a/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_node.cc
@@ -88,13 +88,14 @@
       MediaStreamAudioDestinationHandler::Create(
           *this, number_of_channels, audio_source_ptr));
   SendLogMessage(
-      __func__, UNSAFE_TODO(String::Format(
-                    "({context.state=%s}, {context.sampleRate=%.0f}, "
-                    "{number_of_channels=%u}, {handler=0x%" PRIXPTR
-                    "}, [this=0x%" PRIXPTR "])",
-                    context.state().AsCStr(), context.sampleRate(),
-                    number_of_channels, reinterpret_cast<uintptr_t>(&Handler()),
-                    reinterpret_cast<uintptr_t>(this))));
+      __func__,
+      String::Format("({context.state=%s}, {context.sampleRate=%.0f}, "
+                     "{number_of_channels=%u}, {handler=0x%" PRIXPTR
+                     "}, [this=0x%" PRIXPTR "])",
+                     context.state().AsString().Utf8().c_str(),
+                     context.sampleRate(), number_of_channels,
+                     reinterpret_cast<uintptr_t>(&Handler()),
+                     reinterpret_cast<uintptr_t>(this)));
 }
 
 MediaStreamAudioDestinationNode* MediaStreamAudioDestinationNode::Create(
@@ -167,10 +168,11 @@
 }
 
 void MediaStreamAudioDestinationNode::SendLogMessage(
-    const char* const function_name,
+    const String& function_name,
     const String& message) {
-  WebRtcLogMessage(UNSAFE_TODO(String::Format("[WA]MSADN::%s %s", function_name,
-                                              message.Utf8().c_str()))
+  WebRtcLogMessage(String::Format("[WA]MSADN::%s %s",
+                                  function_name.Utf8().c_str(),
+                                  message.Utf8().c_str())
                        .Utf8());
 }
 
diff --git a/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_node.h b/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_node.h
index 4a2b948..0e160c7 100644
--- a/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_node.h
+++ b/third_party/blink/renderer/modules/webaudio/media_stream_audio_destination_node.h
@@ -73,7 +73,7 @@
   MediaStreamAudioDestinationHandler& GetOwnHandler() const;
 
   // https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/media/capture/README.md#logs
-  void SendLogMessage(const char* const function_name, const String& message);
+  void SendLogMessage(const String& function_name, const String& message);
 
   Member<MediaStreamSource> source_;
   Member<MediaStream> stream_;
diff --git a/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_handler.cc b/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_handler.cc
index 37d57d3..cacf07aa 100644
--- a/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_handler.cc
+++ b/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_handler.cc
@@ -126,14 +126,13 @@
   }
 }
 
-void MediaStreamAudioSourceHandler::SendLogMessage(
-    const char* const function_name,
-    const String& message) {
-  WebRtcLogMessage(
-      UNSAFE_TODO(String::Format("[WA]MSASH::%s %s [this=0x%" PRIXPTR "]",
-                                 function_name, message.Utf8().c_str(),
-                                 reinterpret_cast<uintptr_t>(this)))
-          .Utf8());
+void MediaStreamAudioSourceHandler::SendLogMessage(const String& function_name,
+                                                   const String& message) {
+  WebRtcLogMessage(String::Format("[WA]MSASH::%s %s [this=0x%" PRIXPTR "]",
+                                  function_name.Utf8().c_str(),
+                                  message.Utf8().c_str(),
+                                  reinterpret_cast<uintptr_t>(this))
+                       .Utf8());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_handler.h b/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_handler.h
index fc627a5..2c901744 100644
--- a/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_handler.h
+++ b/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_handler.h
@@ -40,7 +40,7 @@
   bool PropagatesSilence() const override { return false; }
 
   // https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/media/capture/README.md#logs
-  void SendLogMessage(const char* const function_name, const String& message);
+  void SendLogMessage(const String& function_name, const String& message);
 
   std::unique_ptr<AudioSourceProvider> audio_source_provider_;
 
diff --git a/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.cc b/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.cc
index 9aa16ac4..80090eef 100644
--- a/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.cc
@@ -151,11 +151,12 @@
   return static_cast<MediaStreamAudioSourceHandler&>(Handler());
 }
 
-void MediaStreamAudioSourceNode::SendLogMessage(const char* const function_name,
+void MediaStreamAudioSourceNode::SendLogMessage(const String& function_name,
                                                 const String& message) {
-  WebRtcLogMessage(UNSAFE_TODO(
-      String::Format("[WA]MSASN::%s %s", function_name, message.Utf8().c_str())
-          .Utf8()));
+  WebRtcLogMessage(String::Format("[WA]MSASN::%s %s",
+                                  function_name.Utf8().c_str(),
+                                  message.Utf8().c_str())
+                       .Utf8());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.h b/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.h
index 84c742a..d4135fb 100644
--- a/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.h
+++ b/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.h
@@ -79,7 +79,7 @@
   MediaStreamAudioSourceHandler& GetMediaStreamAudioSourceHandler() const;
 
   // https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/media/capture/README.md#logs
-  void SendLogMessage(const char* const function_name, const String& message);
+  void SendLogMessage(const String& function_name, const String& message);
 
   Member<MediaStreamTrack> audio_track_;
   Member<MediaStream> media_stream_;
diff --git a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_handler.cc b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_handler.cc
index 9a5eda5..1cc00b0 100644
--- a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_handler.cc
+++ b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_handler.cc
@@ -541,14 +541,13 @@
 }
 
 void RealtimeAudioDestinationHandler::SendLogMessage(
-    const char* const function_name,
+    const String& function_name,
     const String& message) const {
-  WebRtcLogMessage(
-      UNSAFE_TODO(String::Format("[WA]RADH::%s %s (sink_descriptor_=%s)",
-                                 function_name, message.Utf8().c_str(),
-                                 sink_descriptor_.SinkId().Utf8().c_str()))
-          .Utf8()
-          .c_str());
+  WebRtcLogMessage(String::Format("[WA]RADH::%s %s (sink_descriptor_=%s)",
+                                  function_name.Utf8().c_str(),
+                                  message.Utf8().c_str(),
+                                  sink_descriptor_.SinkId().Utf8().c_str())
+                       .Utf8());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_handler.h b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_handler.h
index e5234be..b182108 100644
--- a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_handler.h
+++ b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_handler.h
@@ -127,8 +127,7 @@
   }
 
   // https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/media/capture/README.md#logs
-  void SendLogMessage(const char* const function_name,
-                      const String& message) const;
+  void SendLogMessage(const String& function_name, const String& message) const;
 
   // Stores a sink descriptor for sink transition.
   WebAudioSinkDescriptor sink_descriptor_;
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 90b57dd..4de9c105 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -889,6 +889,7 @@
     "graphics/box_reflection.h",
     "graphics/canvas_2d_color_params.cc",
     "graphics/canvas_2d_color_params.h",
+    "graphics/canvas_child_paint_record.h",
     "graphics/canvas_deferred_paint_record.cc",
     "graphics/canvas_deferred_paint_record.h",
     "graphics/canvas_hibernation_handler.cc",
diff --git a/third_party/blink/renderer/platform/audio/audio_destination.cc b/third_party/blink/renderer/platform/audio/audio_destination.cc
index 1cac049..917f12ff 100644
--- a/third_party/blink/renderer/platform/audio/audio_destination.cc
+++ b/third_party/blink/renderer/platform/audio/audio_destination.cc
@@ -68,7 +68,7 @@
 // to play audio via Web Audio API.
 constexpr uint32_t kFIFOSize = 128 * 128;
 
-const char* DeviceStateToString(AudioDestination::DeviceState state) {
+const String DeviceStateToString(AudioDestination::DeviceState state) {
   switch (state) {
     case AudioDestination::kRunning:
       return "running";
@@ -443,9 +443,11 @@
                                 callback_buffer_size_));
   SendLogMessage(__func__, String::Format("=> (device sample rate=%.0f Hz)",
                                           web_audio_device_->SampleRate()));
-  SendLogMessage(__func__, UNSAFE_TODO(String::Format(
-                               "Output buffer bypass: %s",
-                               is_output_buffer_bypassed_ ? "yes" : "no")));
+  if (is_output_buffer_bypassed_) {
+    SendLogMessage(__func__, "Output buffer bypass: yes");
+  } else {
+    SendLogMessage(__func__, "Output buffer bypass: no");
+  }
 
   TRACE_EVENT1("webaudio", "AudioDestination::AudioDestination",
                "sink information",
@@ -681,12 +683,13 @@
   frames_elapsed_ += previous_platform_destination->FramesElapsed();
 }
 
-void AudioDestination::SendLogMessage(const char* const function_name,
+void AudioDestination::SendLogMessage(const String& function_name,
                                       const String& message) const {
-  WebRtcLogMessage(UNSAFE_TODO(
-      String::Format("[WA]AD::%s %s [state=%s]", function_name,
-                     message.Utf8().c_str(), DeviceStateToString(device_state_))
-          .Utf8()));
+  WebRtcLogMessage(
+      String::Format("[WA]AD::%s %s [state=%s]", function_name.Utf8().c_str(),
+                     message.Utf8().c_str(),
+                     DeviceStateToString(device_state_).Utf8().c_str())
+          .Utf8());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/audio/audio_destination.h b/third_party/blink/renderer/platform/audio/audio_destination.h
index 36babd5..b31c8ed 100644
--- a/third_party/blink/renderer/platform/audio/audio_destination.h
+++ b/third_party/blink/renderer/platform/audio/audio_destination.h
@@ -202,8 +202,7 @@
   void PullFromCallback(AudioBus* destination_bus, base::TimeDelta delay);
 
   // https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/media/capture/README.md#logs
-  void SendLogMessage(const char* const function_name,
-                      const String& message) const;
+  void SendLogMessage(const String& function_name, const String& message) const;
 
   // Accessed by the main thread.
   std::unique_ptr<WebAudioDevice> web_audio_device_;
diff --git a/third_party/blink/renderer/platform/graphics/canvas_child_paint_record.h b/third_party/blink/renderer/platform/graphics/canvas_child_paint_record.h
new file mode 100644
index 0000000..8d57e14
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/canvas_child_paint_record.h
@@ -0,0 +1,22 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_CANVAS_CHILD_PAINT_RECORD_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_CANVAS_CHILD_PAINT_RECORD_H_
+
+#include "cc/paint/paint_record.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "ui/gfx/geometry/size_f.h"
+
+namespace blink {
+
+struct PLATFORM_EXPORT CanvasChildPaintRecord {
+  float scale = 1.f;
+  gfx::SizeF box_size;
+  cc::PaintRecord record;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_CANVAS_CHILD_PAINT_RECORD_H_
diff --git a/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.cc b/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.cc
index 254b16f..a5cc0ae3 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.cc
@@ -96,7 +96,8 @@
 }
 
 void ContentLayerClientImpl::UpdateCcPictureLayer(
-    const PendingLayer& pending_layer) {
+    const PendingLayer& pending_layer,
+    PropertyTreeState property_state_for_paint) {
   const auto& paint_chunks = pending_layer.Chunks();
   CHECK_EQ(cc_picture_layer_->client(), this);
 #if EXPENSIVE_DCHECKS_ARE_ON()
@@ -136,6 +137,10 @@
             paint_chunks[0].id.client_id));
   }
 
+  canvas_child_box_size_ = layer_state.Effect().CanvasChildBoxSize();
+  canvas_child_effective_zoom_ =
+      layer_state.Effect().CanvasChildEffectiveZoom();
+
   // Note: cc::Layer API assumes the layer bounds start at (0, 0), but the
   // bounding box of a paint chunk does not necessarily start at (0, 0) (and
   // could even be negative). Internally the generated layer translates the
@@ -168,7 +173,7 @@
   auto previous_display_list = std::move(cc_display_item_list_);
   cc_display_item_list_ = base::MakeRefCounted<cc::DisplayItemList>();
   PaintChunksToCcLayer::ConvertInto(
-      paint_chunks, layer_state, layer_offset,
+      paint_chunks, property_state_for_paint, layer_offset,
       base::OptionalToPtr(raster_under_invalidation_params),
       *cc_display_item_list_);
 
@@ -237,6 +242,20 @@
   cc_picture_layer_->SetNeedsDisplayRect(rect);
 }
 
+CanvasChildPaintRecord ContentLayerClientImpl::GetCanvasChildPaintRecord()
+    const {
+  gfx::Vector2dF offset = cc_picture_layer_->offset_to_transform_parent();
+  if (offset.IsZero()) {
+    return {canvas_child_effective_zoom_, canvas_child_box_size_,
+            cc_display_item_list_->paint_op_buffer().DeepCopyAsRecord()};
+  }
+  auto result = sk_make_sp<cc::PaintOpBuffer>();
+  result->push<cc::TranslateOp>(offset.x(), offset.y());
+  *result += cc_display_item_list_->paint_op_buffer();
+  return {canvas_child_effective_zoom_, canvas_child_box_size_,
+          result->ReleaseAsRecord()};
+}
+
 size_t ContentLayerClientImpl::ApproximateUnsharedMemoryUsage() const {
   return sizeof(*this) + raster_invalidator_->ApproximateUnsharedMemoryUsage() -
          sizeof(raster_invalidator_);
diff --git a/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h b/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h
index 1fc81eb2..7e70338 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h
@@ -8,6 +8,7 @@
 #include "base/dcheck_is_on.h"
 #include "cc/layers/content_layer_client.h"
 #include "cc/layers/picture_layer.h"
+#include "third_party/blink/renderer/platform/graphics/canvas_child_paint_record.h"
 #include "third_party/blink/renderer/platform/graphics/compositing/layers_as_json.h"
 #include "third_party/blink/renderer/platform/graphics/paint/raster_invalidator.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -44,12 +45,15 @@
 
   cc::Layer& Layer() const { return *cc_picture_layer_.get(); }
 
-  void UpdateCcPictureLayer(const PendingLayer&);
+  void UpdateCcPictureLayer(const PendingLayer&,
+                            PropertyTreeState property_state_for_paint);
 
   bool HasRasterInducingScroll() const;
 
   RasterInvalidator& GetRasterInvalidator() { return *raster_invalidator_; }
 
+  CanvasChildPaintRecord GetCanvasChildPaintRecord() const;
+
   size_t ApproximateUnsharedMemoryUsage() const;
 
  private:
@@ -59,6 +63,8 @@
   scoped_refptr<cc::PictureLayer> cc_picture_layer_;
   scoped_refptr<cc::DisplayItemList> cc_display_item_list_;
   Member<RasterInvalidator> raster_invalidator_;
+  gfx::SizeF canvas_child_box_size_;
+  float canvas_child_effective_zoom_;
   // Used during UpdateCcPictureLayer().
   bool has_empty_invalidations_ = false;
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 3d23450..2696f9c 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -105,6 +105,17 @@
   }
 }
 
+std::optional<CanvasChildPaintRecord>
+PaintArtifactCompositor::GetCanvasChildPaintRecord(DOMNodeId child_id) const {
+  auto it = canvas_child_layer_map_.find(child_id);
+  if (it == canvas_child_layer_map_.end()) {
+    return std::nullopt;
+  }
+  auto& pending_layer =
+      pending_layers_[canvas_child_layer_map_.find(child_id)->value];
+  return pending_layer.GetCanvasChildPaintRecord();
+}
+
 void PaintArtifactCompositor::WillBeRemovedFromFrame() {
   root_layer_->RemoveAllChildren();
 }
@@ -512,6 +523,18 @@
   return false;
 }
 
+// When a child element of <canvas> is rendered via drawElementImage, its paint
+// must be recorded using the canvas element's content clip and effect state.
+PropertyTreeState GetPropertyTreeStateForPaint(
+    const PropertyTreeState& layer_state) {
+  PropertyTreeState result = layer_state;
+  if (layer_state.Effect().HasCanvasChildState()) {
+    result.SetClip(layer_state.Effect().CanvasChildContentClip());
+    result.SetEffect(layer_state.Effect().CanvasChildContentEffect());
+  }
+  return result;
+}
+
 }  // namespace
 
 void PaintArtifactCompositor::SetNeedsUpdateInternal(UpdateType update_type) {
@@ -1021,6 +1044,7 @@
 
   wtf_size_t old_size = pending_layers_.size();
   OldPendingLayerMatcher old_pending_layer_matcher(std::move(pending_layers_));
+  canvas_child_layer_map_.clear();
   CHECK(painted_scroll_translations_.empty());
 
   // Make compositing decisions, storing the result in |pending_layers_|.
@@ -1059,13 +1083,18 @@
   cc::LayerSelection layer_selection;
   HashSet<int> layers_having_text;
   HashSet<int> layers_having_video;
-  for (auto& pending_layer : pending_layers_) {
+  for (wtf_size_t i = 0; i < pending_layers_.size(); i++) {
+    auto& pending_layer = pending_layers_[i];
+    const auto& property_state = pending_layer.GetPropertyTreeState();
+    PropertyTreeState property_state_for_paint =
+        GetPropertyTreeStateForPaint(property_state);
+
     pending_layer.UpdateCompositedLayer(
-        old_pending_layer_matcher.Find(pending_layer), layer_selection,
-        tracks_raster_invalidations_, root_layer_->layer_tree_host());
+        old_pending_layer_matcher.Find(pending_layer), property_state_for_paint,
+        layer_selection, tracks_raster_invalidations_,
+        root_layer_->layer_tree_host());
 
     cc::Layer& layer = pending_layer.CcLayer();
-    const auto& property_state = pending_layer.GetPropertyTreeState();
     const auto& transform = property_state.Transform();
     const auto& clip = property_state.Clip();
     const auto& effect = property_state.Effect();
@@ -1120,7 +1149,11 @@
     layer.SetEffectTreeIndex(effect_id);
     bool backface_hidden = transform.IsBackfaceHidden();
     layer.SetShouldCheckBackfaceVisibility(backface_hidden);
-    layer.SetCanvasChildId(effect.CanvasChildId());
+    if (effect.CanvasChildId()) {
+      canvas_child_layer_map_.Set(effect.CanvasChildId(), i);
+      layer.SetCanvasChildId(
+          CompositorElementIdFromDOMNodeId(effect.CanvasChildId()));
+    }
 
     if (layer.subtree_property_changed())
       root_layer_->SetNeedsCommit();
@@ -1208,8 +1241,10 @@
     case UpdateType::kRepaint: {
       cc::LayerSelection layer_selection;
       for (auto& pending_layer : pending_layers_) {
-        pending_layer.UpdateCompositedLayerForRepaint(repainted_artifact,
-                                                      layer_selection);
+        PropertyTreeState property_state_for_paint =
+            GetPropertyTreeStateForPaint(pending_layer.GetPropertyTreeState());
+        pending_layer.UpdateCompositedLayerForRepaint(
+            repainted_artifact, property_state_for_paint, layer_selection);
       }
       root_layer_->layer_tree_host()->RegisterSelection(layer_selection);
       UpdateDebugInfo();
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
index b5ab345..6bcbffb 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
@@ -217,6 +217,9 @@
 
   void SetTracksRasterInvalidations(bool);
 
+  std::optional<CanvasChildPaintRecord> GetCanvasChildPaintRecord(
+      DOMNodeId child_id) const;
+
   // Called when the local frame view that owns this compositor is
   // going to be removed from its frame.
   void WillBeRemovedFromFrame();
@@ -335,6 +338,7 @@
 
   class OldPendingLayerMatcher;
   PendingLayers pending_layers_;
+  HashMap<DOMNodeId, wtf_size_t> canvas_child_layer_map_;
 
   class Layerizer;
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/pending_layer.cc b/third_party/blink/renderer/platform/graphics/compositing/pending_layer.cc
index fa01d24a..5ec4714 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/pending_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/pending_layer.cc
@@ -637,8 +637,10 @@
   cc_layer_ = std::move(scrollbar_layer);
 }
 
-void PendingLayer::UpdateContentLayer(PendingLayer* old_pending_layer,
-                                      bool tracks_raster_invalidations) {
+void PendingLayer::UpdateContentLayer(
+    PendingLayer* old_pending_layer,
+    PropertyTreeState property_state_for_paint,
+    bool tracks_raster_invalidations) {
   DCHECK(!ChunkRequiresOwnLayer());
   DCHECK(!cc_layer_);
   DCHECK(!content_layer_client_);
@@ -651,7 +653,7 @@
     content_layer_client_->GetRasterInvalidator().SetTracksRasterInvalidations(
         tracks_raster_invalidations);
   }
-  content_layer_client_->UpdateCcPictureLayer(*this);
+  content_layer_client_->UpdateCcPictureLayer(*this, property_state_for_paint);
 }
 
 void PendingLayer::UpdateSolidColorLayer(PendingLayer* old_pending_layer) {
@@ -702,10 +704,12 @@
   return chunks_[solid_color_chunk_index_].background_color.color;
 }
 
-void PendingLayer::UpdateCompositedLayer(PendingLayer* old_pending_layer,
-                                         cc::LayerSelection& layer_selection,
-                                         bool tracks_raster_invalidations,
-                                         cc::LayerTreeHost* layer_tree_host) {
+void PendingLayer::UpdateCompositedLayer(
+    PendingLayer* old_pending_layer,
+    PropertyTreeState property_state_for_paint,
+    cc::LayerSelection& layer_selection,
+    bool tracks_raster_invalidations,
+    cc::LayerTreeHost* layer_tree_host) {
   // This is used during PaintArifactCompositor::CollectPendingLayers() only.
   non_composited_scroll_translations_.clear();
 
@@ -724,7 +728,8 @@
       if (UsesSolidColorLayer()) {
         UpdateSolidColorLayer(old_pending_layer);
       } else {
-        UpdateContentLayer(old_pending_layer, tracks_raster_invalidations);
+        UpdateContentLayer(old_pending_layer, property_state_for_paint,
+                           tracks_raster_invalidations);
       }
       break;
   }
@@ -742,6 +747,7 @@
 
 void PendingLayer::UpdateCompositedLayerForRepaint(
     const PaintArtifact& repainted_artifact,
+    PropertyTreeState property_state_for_paint,
     cc::LayerSelection& layer_selection) {
   // Essentially replace the paint chunks of the pending layer with the
   // repainted chunks in |repainted_artifact|. The pending layer's paint
@@ -778,7 +784,8 @@
         content_layer_client_->GetRasterInvalidator().SetOldPaintArtifact(
             Chunks().GetPaintArtifact());
       } else {
-        content_layer_client_->UpdateCcPictureLayer(*this);
+        content_layer_client_->UpdateCcPictureLayer(*this,
+                                                    property_state_for_paint);
       }
     }
   }
@@ -854,6 +861,14 @@
   return background_color;
 }
 
+std::optional<CanvasChildPaintRecord> PendingLayer::GetCanvasChildPaintRecord()
+    const {
+  if (!content_layer_client_) {
+    return std::nullopt;
+  }
+  return content_layer_client_->GetCanvasChildPaintRecord();
+}
+
 bool PendingLayer::HasVideo() const {
   return Chunks().size() == 1 && FirstPaintChunk().size() == 1 &&
          FirstDisplayItem().GetType() == DisplayItem::kForeignLayerVideo;
diff --git a/third_party/blink/renderer/platform/graphics/compositing/pending_layer.h b/third_party/blink/renderer/platform/graphics/compositing/pending_layer.h
index 8784a88..d6f286b3 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/pending_layer.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/pending_layer.h
@@ -81,6 +81,8 @@
     chunks_.SetPaintArtifact(paint_artifact);
   }
 
+  std::optional<CanvasChildPaintRecord> GetCanvasChildPaintRecord() const;
+
   using IsCompositedScrollFunction =
       PropertyTreeState::IsCompositedScrollFunction;
 
@@ -160,14 +162,17 @@
   // one in |old_pending_layer|, and updates the layer according to the current
   // contents and properties of this PendingLayer.
   void UpdateCompositedLayer(PendingLayer* old_pending_layer,
+                             PropertyTreeState property_state_for_paint,
                              cc::LayerSelection&,
                              bool tracks_raster_invalidations,
                              cc::LayerTreeHost*);
 
   // A lighter version of UpdateCompositedLayer(). Called when the existing
   // composited layer has only repainted since the last update
-  void UpdateCompositedLayerForRepaint(const PaintArtifact& repainted_artifact,
-                                       cc::LayerSelection&);
+  void UpdateCompositedLayerForRepaint(
+      const PaintArtifact& repainted_artifact,
+      PropertyTreeState property_state_for_paint,
+      cc::LayerSelection&);
 
   // Another lighter version of UpdateCompositedLayers(). Called after
   // raster-inducing scrolls that don't need repaint or PaintArtifactCompositor
@@ -211,6 +216,7 @@
   void UpdateScrollHitTestLayer(PendingLayer* old_pending_layer);
   void UpdateScrollbarLayer(PendingLayer* old_pending_layer);
   void UpdateContentLayer(PendingLayer* old_pending_layer,
+                          PropertyTreeState property_state_for_paint,
                           bool tracks_raster_invalidations);
   void UpdateSolidColorLayer(PendingLayer* old_pending_layer);
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc
index 6ffbfd86..4bf51a18 100644
--- a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc
@@ -60,7 +60,7 @@
       view_transition_element_resource_id !=
           other.view_transition_element_resource_id ||
       restriction_target_id != other.restriction_target_id ||
-      canvas_child_id != other.canvas_child_id ||
+      canvas_child_state != other.canvas_child_state ||
       self_or_ancestor_participates_in_view_transition !=
           other.self_or_ancestor_participates_in_view_transition ||
       needs_effect_for_2d_scale_transform !=
@@ -119,6 +119,12 @@
 void EffectPaintPropertyNode::State::Trace(Visitor* visitor) const {
   visitor->Trace(local_transform_space);
   visitor->Trace(output_clip);
+  visitor->Trace(canvas_child_state);
+}
+
+void EffectPaintPropertyNode::CanvasChildState::Trace(Visitor* visitor) const {
+  visitor->Trace(content_effect);
+  visitor->Trace(content_clip);
 }
 
 EffectPaintPropertyNode::EffectPaintPropertyNode(RootTag)
@@ -223,6 +229,18 @@
   return state_.filter_info->operations.MapRect(input_rect);
 }
 
+const EffectPaintPropertyNode&
+EffectPaintPropertyNode::CanvasChildContentEffect() const {
+  CHECK(HasCanvasChildState());
+  return state_.canvas_child_state.content_effect->Unalias();
+}
+
+const ClipPaintPropertyNode& EffectPaintPropertyNode::CanvasChildContentClip()
+    const {
+  CHECK(HasCanvasChildState());
+  return state_.canvas_child_state.content_clip->Unalias();
+}
+
 std::unique_ptr<JSONObject> EffectPaintPropertyNode::ToJSON() const {
   auto json = EffectPaintPropertyNodeOrAlias::ToJSON();
   json->SetString("localTransformSpace",
diff --git a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
index 4248d638..3b6fafec8 100644
--- a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
@@ -20,6 +20,7 @@
 
 namespace blink {
 
+class ClipPaintPropertyNode;
 class ClipPaintPropertyNodeOrAlias;
 class PropertyTreeState;
 class TransformPaintPropertyNodeOrAlias;
@@ -101,6 +102,23 @@
     USING_FAST_MALLOC(BackdropFilterInfo);
   };
 
+  // Used to associate this effect with a direct child of a canvas element for
+  // DrawElementImage.
+  struct PLATFORM_EXPORT CanvasChildState {
+    DISALLOW_NEW();
+
+   public:
+    bool operator==(const CanvasChildState&) const = default;
+
+    DOMNodeId id = kInvalidDOMNodeId;
+    gfx::SizeF box_size;
+    float effective_zoom = 1.f;
+    Member<const EffectPaintPropertyNodeOrAlias> content_effect;
+    Member<const ClipPaintPropertyNodeOrAlias> content_clip;
+
+    void Trace(Visitor*) const;
+  };
+
   // To make it less verbose and more readable to construct and update a node,
   // a struct with default values is used to represent the state.
   struct PLATFORM_EXPORT State {
@@ -135,9 +153,7 @@
     // Used to associate this effect node with its originating Element.
     RestrictionTargetId restriction_target_id;
 
-    // Used to associate this effect with a direct child of a canvas element
-    // for DrawElementImage.
-    CompositorElementId canvas_child_id;
+    CanvasChildState canvas_child_state;
 
     // When set, the affected elements should avoid doing clipping for
     // optimization purposes (like off-screen clipping). This is set by view
@@ -364,10 +380,23 @@
     return state_.restriction_target_id;
   }
 
-  const CompositorElementId& CanvasChildId() const {
-    return state_.canvas_child_id;
+  bool HasCanvasChildState() const {
+    return state_.canvas_child_state.id != kInvalidDOMNodeId;
   }
 
+  DOMNodeId CanvasChildId() const { return state_.canvas_child_state.id; }
+
+  gfx::SizeF CanvasChildBoxSize() const {
+    return state_.canvas_child_state.box_size;
+  }
+
+  float CanvasChildEffectiveZoom() const {
+    return state_.canvas_child_state.effective_zoom;
+  }
+
+  const EffectPaintPropertyNode& CanvasChildContentEffect() const;
+  const ClipPaintPropertyNode& CanvasChildContentClip() const;
+
   bool SelfOrAncestorParticipatesInViewTransition() const {
     return state_.self_or_ancestor_participates_in_view_transition;
   }
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index 3b1efb41..d371bf6c 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -1598,6 +1598,17 @@
     },
     {
         'paths': [
+            'third_party/blink/renderer/core/html/html_credential_element.cc',
+            'third_party/blink/renderer/core/html/html_credential_element.h',
+            'third_party/blink/renderer/core/html/html_login_element.cc',
+            'third_party/blink/renderer/core/html/html_login_element.h',
+        ],
+        'allowed': [
+            'base::Value',
+        ],
+    },
+    {
+        'paths': [
             'third_party/blink/renderer/core/css/style_color.cc',
             'third_party/blink/renderer/core/html/html_capability_element_base.cc',
             'third_party/blink/renderer/core/paint/box_border_painter.cc',
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 0508c70..4468442 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -800,7 +800,10 @@
 
 ### ====== HTML-in-Canvas
 crbug.com/477575513 wpt_internal/html/canvas/drawElementImage/css-backdrop-filter-and-mask.html [ Failure ]
-crbug.com/477575513 wpt_internal/html/canvas/drawElementImage/nested-webgl-canvas.html [ Failure ]
+crbug.com/477575513 wpt_internal/html/canvas/drawElementImage/nested-webgl-canvas.html [ Skip Timeout ]
+crbug.com/435609887 wpt_internal/html/canvas/drawElementImage/nested-canvas.html [ Skip Timeout ]
+crbug.com/435609887 wpt_internal/html/canvas/drawElementImage/nested-div-canvas.html [ Skip Timeout ]
+crbug.com/480074850 virtual/canvas-draw-element-in-subtree/draw-element-grandchild.html [ Failure Skip ]
 
 # WPT speculative-parsing tests failing
 crbug.com/1144176 external/wpt/html/syntax/speculative-parsing/generated/document-write/link-rel-alternate-stylesheet.tentative.sub.html [ Crash Failure ]
@@ -2870,7 +2873,9 @@
 crbug.com/476344902 virtual/webui-browser/external/wpt/html/browsers/windows/nested-browsing-contexts/window-top.html [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
-external/wpt/webdriver/tests/bidi/emulation/set_network_conditions/user_contexts.py [ Failure ]
+crbug.com/488373052 external/wpt/css/css-values/attr-namespace-non-existing.html [ Failure ]
+crbug.com/488373052 external/wpt/css/css-values/attr-namespace-valid.xhtml [ Failure ]
+crbug.com/488362582 external/wpt/webdriver/tests/bidi/emulation/set_network_conditions/user_contexts.py [ Failure ]
 crbug.com/488130147 [ Win ] external/wpt/WebCryptoAPI/encrypt_decrypt/aes_cbc.https.any.html [ Pass Timeout ]
 crbug.com/488130147 [ Win ] external/wpt/WebCryptoAPI/sign_verify/ecdsa.https.any.html [ Pass Timeout ]
 crbug.com/488171521 external/wpt/navigation-api/navigate-event/navigate-history-traversal-during-onnavigate-should-reject.html [ Timeout ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index c2d679ff..5f9e1bc2 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -6269,5 +6269,20 @@
       "--enable-features=WebAudioConfigurableRenderQuantum"
     ],
     "expires": "never"
+  },
+  {
+    "prefix": "single-axis-scroll-containers",
+    "platforms": ["Linux", "Mac", "Win", "Android"],
+    "bases": [
+      "external/wpt/css/css-position/sticky/position-sticky-single-axis-basic.html",
+      "external/wpt/css/css-position/sticky/position-sticky-single-axis-dynamic.html",
+      "external/wpt/css/css-position/sticky/position-sticky-single-axis-nested.html"
+    ],
+    "exclusive_tests": "ALL",
+    "args": ["--enable-blink-features=SingleAxisScrollContainers"],
+    "owners": [
+      "freedebreuil@google.com"
+    ],
+    "expires": "Jan 1, 2027"
   }
 ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 710a2e4..4ce8e9f6 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -77821,6 +77821,19 @@
         {}
        ]
       ],
+      "align-out-of-flow-only-content.html": [
+       "cd7750b8cdd29b341f5bf3663a512305fd6e0763",
+       [
+        null,
+        [
+         [
+          "/css/css-align/abspos/align-out-of-flow-only-content-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "align-self-static-position-001.html": [
        "1ff73ef116d9d59786bf91c73c546a3f0f7e89df",
        [
@@ -161658,6 +161671,19 @@
         {}
        ]
       ],
+      "custom-highlight-painting-line-wrap-001.html": [
+       "8a9bbeb1c1ef87e5289f741f69bcdf7d94f5a7f3",
+       [
+        null,
+        [
+         [
+          "/css/css-highlight-api/painting/custom-highlight-painting-line-wrap-001-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "custom-highlight-painting-overlapping-highlights-001.html": [
        "1129d369832bf53db7e9fc0ae87a1ecd5c19a0af",
        [
@@ -270732,6 +270758,45 @@
        {}
       ]
      ],
+     "attr-namespace-non-existing.html": [
+      "c1abc431e655ed37566264e42b66f20632ee2bb2",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "attr-namespace-valid.xhtml": [
+      "f5fdc184942b20cf98c2d92c08459944ba2f086a",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "attr-namespace-wildcard.html": [
+      "f9be5a7d8f46b6fdadac10751423f5b7d95cb733",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "attr-notype-fallback.html": [
       "b88f3de4b571136c53c74486e6b63682bbd3291c",
       [
@@ -281883,6 +281948,19 @@
         {}
        ]
       ],
+      "svg-stroke-width.html": [
+       "ffc70ccad80f9497309ee0cabd445356bde95967",
+       [
+        null,
+        [
+         [
+          "/css/css-viewport/zoom/svg-stroke-width-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "svg-transform.html": [
        "e0adf0725e86b43d2b73e7121704631012236988",
        [
@@ -325919,7 +325997,7 @@
          ]
         ],
         "select-explicit-size.html": [
-         "71c77d266d94feb2fd80837e3aa47bdfe2348cd9",
+         "970c1ebbc8561daad21c88c72d94f431e2b204bb",
          [
           null,
           [
@@ -325928,7 +326006,23 @@
             "=="
            ]
           ],
-          {}
+          {
+           "fuzzy": [
+            [
+             null,
+             [
+              [
+               0,
+               2
+              ],
+              [
+               0,
+               1
+              ]
+             ]
+            ]
+           ]
+          }
          ]
         ],
         "select-font-size.html": [
@@ -360520,6 +360614,10 @@
        "fd7093f98748709d0f6d03c9dc9993a7a1447446",
        []
       ],
+      "align-out-of-flow-only-content-ref.html": [
+       "7c69ac3bca6ef7190b7d2fb5c1ab0416bbf4ffa4",
+       []
+      ],
       "align-self-static-position-001-ref.html": [
        "94d0b43b2127080e961be8a7277865e2b6013d88",
        []
@@ -382846,6 +382944,10 @@
        "88f3d0f3d65cf46cc56fae34166d6c38db170eb2",
        []
       ],
+      "custom-highlight-painting-line-wrap-001-ref.html": [
+       "f60a246c908470e1585050ac57eafd3187a3d733",
+       []
+      ],
       "custom-highlight-painting-overlapping-highlights-001-ref.html": [
        "3c08ad55aefe3cbb6670250b7c069e897f805977",
        []
@@ -406688,6 +406790,10 @@
        "f5f349517c114b76e6a59b8eca4b5041a4e00e4f",
        []
       ],
+      "svg-stroke-width-ref.html": [
+       "14980cb77c5a2b324a1ba888fe72b04f0a7a3c0e",
+       []
+      ],
       "svg-transform-ref.html": [
        "abaed2accea09b8d4d6b790bab554ca83ac630ba",
        []
@@ -544824,6 +544930,20 @@
        {}
       ]
      ],
+     "select-as-flex-item-child-with-overflow.html": [
+      "394eb00e954141bcfe357772ae9ad278b9886144",
+      [
+       null,
+       {}
+      ]
+     ],
+     "select-as-flex-item-with-overflow.html": [
+      "d53c5309ef384d7e31d4c9cb044835238b332fff",
+      [
+       null,
+       {}
+      ]
+     ],
      "shrinking-column-flexbox.html": [
       "680dc7eb7f36e508437fab444660d58d03f00bfe",
       [
@@ -574684,7 +574804,7 @@
        ]
       ],
       "svg-computed-style.html": [
-       "fe7b69ac1544f4c3836133c915b5e8a747d5daf2",
+       "03df4792fb299ce3623a01820e44d16bb5209af8",
        [
         null,
         {}
@@ -588068,6 +588188,13 @@
        }
       ]
      ],
+     "Range-extractContents-dynamic-end.html": [
+      "71fcc9d7a4072dda74efa29f131600fad4e74622",
+      [
+       null,
+       {}
+      ]
+     ],
      "Range-extractContents.html": [
       "88f8fa55f8652fc189b4205f83f0b413e4fb7503",
       [
@@ -685356,7 +685483,7 @@
       "focusgroup": {
        "tentative": {
         "ax-role-inference-children.html": [
-         "04e3a7d819a21473c2d90c91cd5d251ad93095ed",
+         "a26b108d1b2dd380b3f7afa24b759206e0d4c95a",
          [
           null,
           {
@@ -685401,7 +685528,7 @@
         ],
         "backward-navigation": {
          "does-not-move-when-on-focusgroup-root.html": [
-          "97de0f7245df17bb3dbecbfadf826ccc9e25442a",
+          "00c7d5ca4a53caf88c82d172c8803725d8fbd797",
           [
            null,
            {
@@ -685410,7 +685537,7 @@
           ]
          ],
          "does-not-move-when-on-non-focusgroup-item.html": [
-          "bce137775608e3dd46ade6357ca3c80acb002303",
+          "c1f6dcb48b7aab579bbac29651412b2471946b3f",
           [
            null,
            {
@@ -685419,7 +685546,7 @@
           ]
          ],
          "does-not-move-when-only-one-item-and-wraps.html": [
-          "b31da027c9c19387a5c97f3f1733c87a27db2dee",
+          "d43146266b19f77a8182971b2600a3f1a6913d68",
           [
            null,
            {
@@ -685428,7 +685555,7 @@
           ]
          ],
          "does-not-move-when-only-one-item.html": [
-          "4bf74c3d452e8979fe90dd33f881711ffb716f99",
+          "1a54a0a0b68b72661951a5623245676dc324fc6f",
           [
            null,
            {
@@ -685437,7 +685564,7 @@
           ]
          ],
          "does-not-move-when-outside-focusgroup.html": [
-          "8bbe959f90fd5206cf158095a45889278761169c",
+          "3818bebfff0a55d5d9de4e053dada8a9351cd15d",
           [
            null,
            {
@@ -685446,7 +685573,7 @@
           ]
          ],
          "does-not-wrap-when-not-supported.html": [
-          "ea243d55da7eb231c82532ce2851387cddbd9b73",
+          "27c5bd3eff97d436cc8082e8e54a82aa1bcb38b7",
           [
            null,
            {
@@ -685502,7 +685629,7 @@
           ]
          },
          "moves-to-previous-item-and-skips-focusable-item.html": [
-          "979ec15ae5cde4d624129b42f00f1d89cd850bde",
+          "cf52044e23cefef35d11894b79ede46bdf04739d",
           [
            null,
            {
@@ -685511,7 +685638,7 @@
           ]
          ],
          "moves-to-previous-item.html": [
-          "bb9def8eefa771c13187f970b900e5476b46b438",
+          "fcc10ff46ebd2ceb4aac83475664302e871a33e3",
           [
            null,
            {
@@ -685520,7 +685647,7 @@
           ]
          ],
          "skips-non-focusgroup-subtree.html": [
-          "316fb9fb83b264b0523c41887c0a1f68b895cecb",
+          "06f2dea058a0ea8413156b38ee71756a72908a72",
           [
            null,
            {
@@ -685529,7 +685656,7 @@
           ]
          ],
          "skips-root-focusgroup-complex-case.html": [
-          "cf628ce988447882797576a0dc0bbc57a90458d5",
+          "1c800423a9cb440a2cb02c6b1a32a22fc399e626",
           [
            null,
            {
@@ -685538,7 +685665,7 @@
           ]
          ],
          "skips-root-focusgroup.html": [
-          "c3f27db4b01bc70a50e5a92bad6ff8341144d1a7",
+          "d87cc55af63a5fb0e784258719d490ebb193278a",
           [
            null,
            {
@@ -685594,7 +685721,7 @@
           ]
          },
          "wraps-successfully-complex-case.html": [
-          "6129ac6625b4230b99d973116368418fc5475ceb",
+          "d6454b059a1d4d6de3a442b10c7257a66db7539b",
           [
            null,
            {
@@ -685603,7 +685730,7 @@
           ]
          ],
          "wraps-successfully.html": [
-          "600daad1bf93f3317b8633aac4b2ce5e695d40dd",
+          "97d765a8e73c754dfc0893b00c508568056ba368",
           [
            null,
            {
@@ -685622,7 +685749,7 @@
          ]
         ],
         "behavior-tokens-comprehensive.html": [
-         "b2a6841333a93427c0768bacba9d35a7d8c35d14",
+         "0af5c4882f1485f0228a5eabe170d713bf42bbf6",
          [
           null,
           {
@@ -685632,7 +685759,7 @@
         ],
         "descendant-navigation": {
          "deeply-nested-items.html": [
-          "d66a9632d861ce30676179c899eedad0d45cebc0",
+          "0bb26224812d8ca407f41ace654e1e2d6b6613f1",
           [
            null,
            {
@@ -685641,7 +685768,7 @@
           ]
          ],
          "mixed-content-navigation.html": [
-          "a64820c8e229459e3a909dc9f170866c2794bfc0",
+          "dfce06e08a5069f34fe9814dfb916048a02a7d63",
           [
            null,
            {
@@ -685659,7 +685786,7 @@
           ]
          ],
          "various-element-types.html": [
-          "c1a7301a788b7c46dd79990946b36d38369a4030",
+          "0255cb31c62a5137f134ca7597a9f98b7fae18d7",
           [
            null,
            {
@@ -685668,7 +685795,7 @@
           ]
          ],
          "wrapping-with-descendants.html": [
-          "a1f1422c5461a087147db142efb3a32e7ca4b4eb",
+          "7e02373616f90749cd49881048922f1b72eecba5",
           [
            null,
            {
@@ -685679,7 +685806,7 @@
         },
         "forward-navigation": {
          "does-not-move-when-on-focusgroup-root.html": [
-          "a02151294768fad460d5240ee7c29227a4638e5b",
+          "f2ebe21afacd5c321ed1bb5517b83356e8b11ec1",
           [
            null,
            {
@@ -685688,7 +685815,7 @@
           ]
          ],
          "does-not-move-when-on-non-item.html": [
-          "f6d8ac2aa5343e56e7d8a07b312bf1e32dab043b",
+          "0c58bff90f705f198a73ed895a40ecc24ce12d02",
           [
            null,
            {
@@ -685697,7 +685824,7 @@
           ]
          ],
          "does-not-move-when-only-one-item-and-wraps.html": [
-          "34cd12360def9ba795cfe8bd98a516c838e9b0ca",
+          "290412894357da33a6f4d4648b12824e32f85f56",
           [
            null,
            {
@@ -685706,7 +685833,7 @@
           ]
          ],
          "does-not-move-when-only-one-item.html": [
-          "4e3dbf0b0a61e0b17edb8b05d50e061279935f0a",
+          "9373b00cd0d8b1488d4210f9d2ca56216b386b04",
           [
            null,
            {
@@ -685715,7 +685842,7 @@
           ]
          ],
          "does-not-move-when-outside-focusgroup.html": [
-          "446a170db144fc1abf4f33eba3c10bdc2475f9f4",
+          "59f06ec18db72705513e6870a55e9efc827e5714",
           [
            null,
            {
@@ -685724,7 +685851,7 @@
           ]
          ],
          "does-not-wrap-when-not-supported.html": [
-          "e2a79ebd33700a6cb296b1580770531dbe74175f",
+          "e453d095428b4625ffef71a8a8bb516537d14d16",
           [
            null,
            {
@@ -685771,7 +685898,7 @@
           ]
          ],
          "moves-to-next-item-and-skips-non-focusable.html": [
-          "221d1c36ed097898cdfe65cc6f99a087e07993be",
+          "8417daece39e7cd82dcee7fb86c9456b7ff4b883",
           [
            null,
            {
@@ -685780,7 +685907,7 @@
           ]
          ],
          "moves-to-next-item.html": [
-          "0072d8eb5f454ca7bd1ac6a1231d410f7f7db184",
+          "c43cbce2c11c3899a545dac8c87e12e6ece67d92",
           [
            null,
            {
@@ -685789,7 +685916,7 @@
           ]
          ],
          "nested-focusgroup-is-item-of-parent.html": [
-          "25912f34d410624496e17e256e4c2193576b0185",
+          "b1269b6b696d8896d438d1c508c592939fcf3cfc",
           [
            null,
            {
@@ -685798,7 +685925,7 @@
           ]
          ],
          "rtl-direction-reverses-inline.html": [
-          "8d093342343963e3d704e015ad3d477d61596c6b",
+          "5fb8ab16c832d5e360e6d55799f418965c501244",
           [
            null,
            {
@@ -685836,7 +685963,7 @@
           ]
          },
          "vertical-writing-mode.html": [
-          "0b25ea98c79097f2854ffae7fe98bb417b5b9caf",
+          "b376f12be446fd635ba49c737bbc85e75cc8e5f1",
           [
            null,
            {
@@ -685845,7 +685972,7 @@
           ]
          ],
          "wraps-successfully.html": [
-          "5e558a6851a982e2f5f01b454d5ade16fbd0991e",
+          "0d6cf9ba8881c61aec4883e9be52891ab4abb9e5",
           [
            null,
            {
@@ -686030,7 +686157,7 @@
         },
         "opt-out-barriers": {
          "complex-nested-opt-out.html": [
-          "413f8662e4e4870a7546e71aa465d76af6071974",
+          "bd3b3c1bd42ffa62833ae2a071adeed63fcd0f16",
           [
            null,
            {
@@ -686039,7 +686166,7 @@
           ]
          ],
          "none-creates-barriers.html": [
-          "b169ac6977e5aff3d3dbe02aa4f55e7c4bac2002",
+          "26cbfda22044323e84e45f6a4d566116880258be",
           [
            null,
            {
@@ -686133,7 +686260,7 @@
           ]
          ],
          "arrow-key-handler-nested-focusgroup.html": [
-          "74e28106b45d07dc2282ca2b920f3afc9a450d3f",
+          "15c8dc822d03df659b5a6aaeabcf514c1bc67f25",
           [
            null,
            {
@@ -686151,7 +686278,7 @@
           ]
          ],
          "arrow-key-handler-scrollable-container.html": [
-          "6b69441b481293f2a3af1ec9ad4725deddcd30dd",
+          "6e61344a70491ce553af92726c8c2100fada4107",
           [
            null,
            {
@@ -686160,7 +686287,7 @@
           ]
          ],
          "arrow-key-handler-tab-escape.html": [
-          "fbe52722328e5387bb1957a30a345bffb61d7891",
+          "1e4beb3fe923baa60605b8c9a23f7cf96fe14dff",
           [
            null,
            {
@@ -686187,7 +686314,7 @@
           ]
          ],
          "basic-tab-behavior.html": [
-          "e090232618781ccdc0ef0a90df180f8a1ae924fe",
+          "f53054652b279b1833164757bdb28d608582b4ac",
           [
            null,
            {
@@ -686196,7 +686323,7 @@
           ]
          ],
          "dynamic-changes.html": [
-          "77b92f5002589094b60dea686fe56f979721c4a3",
+          "c5b17847d778b2d8f91b268c1ce29bb0dbd861ce",
           [
            null,
            {
@@ -686214,7 +686341,7 @@
           ]
          ],
          "focusgroup-segments.html": [
-          "53f47d248dffcd89353149637194e6be806bbdf6",
+          "b72196cafd3a0d20a9a5c97b33af0dd08bf7e23a",
           [
            null,
            {
@@ -686223,7 +686350,7 @@
           ]
          ],
          "guaranteed-tab-stop-priority.html": [
-          "2bbf50ee0ba19a2ee39c648c9e557f238d79e699",
+          "5bc85e639174230ecaf820a7bba365305b311825",
           [
            null,
            {
@@ -686232,7 +686359,7 @@
           ]
          ],
          "memory-behavior.html": [
-          "d04706e94b45b93adc34627280211225105fa10c",
+          "269afcef98cd4152980ded369a52b876ef38ef89",
           [
            null,
            {
@@ -686241,7 +686368,7 @@
           ]
          ],
          "nested-focusgroups.html": [
-          "15ea05eda65803a02d11c3ed6250543c731ae079",
+          "6ca62a35e57747ed9d19eb2999e7e124f1dad00c",
           [
            null,
            {
@@ -686261,7 +686388,7 @@
           ]
          ],
          "shadow-nested-scope.html": [
-          "35b4b68d04c29eb08caabf673b09a11c01507cff",
+          "cf4c0771b255790ea4ff909a7da9a59f332fd6ce",
           [
            null,
            {
@@ -696516,7 +696643,7 @@
          ]
         ],
         "switch-picker-appearance.html": [
-         "272ebae2186cc67487a428f230181dffbe4fb549",
+         "9abb8d6132fb3fb0a271c99d12e75ffedd10fbe3",
          [
           null,
           {
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/navigation-redirect.sub.window.js b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/navigation-redirect.sub.window.js
new file mode 100644
index 0000000..8479ac3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/navigation-redirect.sub.window.js
@@ -0,0 +1,65 @@
+// META: script=/common/get-host-info.sub.js
+//
+// The following tests assume the policy `Connection-Allowlist: (response-origin)` has been set.
+// Redirects from a connection-allowlisted URL should be blocked by default.
+
+const port = get_host_info().HTTP_PORT_ELIDED;
+const SUCCESS = true;
+const FAILURE = false;
+
+function redirect_test(origin, target_origin, expectation) {
+  promise_test(async t => {
+    const iframe = document.createElement("iframe");
+    let received_message = false;
+    const handler = (e) => {
+      if (e.data === "loaded") {
+        received_message = true;
+      }
+    };
+    window.addEventListener("message", handler);
+    t.add_cleanup(() => window.removeEventListener("message", handler));
+
+    const p = new Promise((resolve) => {
+      iframe.onload = () => {
+        // If onload fires, it might be the success page or an error page.
+        // We wait a short bit to ensure any postMessage has time to arrive.
+        step_timeout(() => resolve(), 50);
+      };
+      iframe.onerror = () => resolve();
+    });
+
+    const target_url = target_origin + "/connection-allowlist/tentative/resources/post-message.html";
+    iframe.src = origin + "/common/redirect.py?status=302&location=" + encodeURIComponent(target_url);
+    document.body.appendChild(iframe);
+    await p;
+    document.body.removeChild(iframe);
+
+    if (expectation === SUCCESS) {
+      assert_true(received_message, `Redirect from ${origin} to ${target_origin} should have succeeded.`);
+    } else {
+      assert_false(received_message, `Redirect from ${origin} to ${target_origin} should have failed.`);
+    }
+  }, `Redirect from ${origin} to ${target_origin} should ${expectation === SUCCESS ? "succeed" : "fail"}.`);
+}
+
+// We're loading this page from `http://{{hosts[][]}}`.
+// The connection allowlist header is `Connection-Allowlist: (response-origin)`.
+// Thus, only `http://{{hosts[][]}}` is allowlisted for navigations.
+
+// Redirect from an allowlisted origin (same-origin):
+// origin: http://{{hosts[][]}} (allowed by allowlist)
+// target: http://{{hosts[][]}} (also allowed)
+// This should FAIL because redirects are default-blocked for allowlisted navigations.
+redirect_test("http://{{hosts[][]}}" + port, "http://{{hosts[][]}}" + port, FAILURE);
+
+// Redirect from an allowlisted origin to a different origin:
+// origin: http://{{hosts[][]}} (allowed by allowlist)
+// target: http://{{hosts[alt][]}} (not allowed)
+// This should FAIL.
+redirect_test("http://{{hosts[][]}}" + port, "http://{{hosts[alt][]}}" + port, FAILURE);
+
+// Initial navigation to a non-allowlisted origin:
+// origin: http://{{hosts[alt][]}} (not allowed)
+// This is blocked before the redirect even happens.
+redirect_test("http://{{hosts[alt][]}}" + port, "http://{{hosts[][]}}" + port, FAILURE);
+
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/navigation-redirect.sub.window.js.headers b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/navigation-redirect.sub.window.js.headers
new file mode 100644
index 0000000..b1a058fb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/navigation-redirect.sub.window.js.headers
@@ -0,0 +1 @@
+Connection-Allowlist: (response-origin)
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-out-of-flow-only-content-ref.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-out-of-flow-only-content-ref.html
new file mode 100644
index 0000000..7c69ac3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-out-of-flow-only-content-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<style>
+.container {
+  background: blue;
+  position: relative;
+  width: 100px;
+  height: 100px;
+  display: inline-block;
+  margin-right: 5px;
+}
+
+.child {
+  width: 50px;
+  height: 50px;
+  position: relative;
+  background: green;
+  bottom: -100px;
+}
+</style>
+<div class="container">
+  <div class="child"></div>
+</div>
+<div class="container">
+  <div class="child"></div>
+</div>
+<div class="container">
+  <div class="child"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-out-of-flow-only-content.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-out-of-flow-only-content.html
new file mode 100644
index 0000000..cd7750b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-out-of-flow-only-content.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align/#align-justify-content">
+<link rel="match" href="align-out-of-flow-only-content-ref.html">
+<meta name="assert" content="Ensures that single, absolutely positioned elements take align-content into account.">
+<style>
+.container {
+  background: blue;
+  position: relative;
+  width: 100px;
+  height: 100px;
+  display: inline-block;
+  margin-right: 5px;
+  align-content: end;
+}
+
+.abs {
+  width: 50px;
+  height: 50px;
+  position: absolute;
+  background: green;
+}
+
+.positioned-inline {
+  left: 0;
+}
+
+.positioned-block {
+  bottom: -50px;
+}
+</style>
+<div class="container">
+  <div class="abs"></div>
+</div>
+<div class="container">
+  <div class="abs positioned-block"></div>
+</div>
+<div class="container">
+  <div class="abs positioned-inline"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/select-as-flex-item-child-with-overflow.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/select-as-flex-item-child-with-overflow.html
new file mode 100644
index 0000000..394eb00e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/select-as-flex-item-child-with-overflow.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>select as flex item's child with overflowing content</title>
+<link rel="help" href="http://www.w3.org/TR/css-flexbox-1/" />
+<link rel="stylesheet" href="/fonts/ahem.css">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+.flexBox {
+  display: flex;
+  flex-direction: column;
+  overflow: scroll;
+  width: 100px;
+}
+
+select {
+  font-family: Ahem;
+  width: 100%;
+}
+</style>
+<p>Test passes if the flex box is not scrollable.</p>
+
+<div class=flexBox>
+  <div>
+    <select>
+      <option>this is a long long long long text this is a long long long long text</option>
+    </select>
+  </div>
+</div>
+<script>
+  test(function(t)
+    {
+      const flexBox = document.querySelector('.flexBox');
+      assert_less_than_equal(flexBox.scrollWidth, 100);
+    }, "select content (option) should not trigger scrollable overflow.");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/select-as-flex-item-with-overflow.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/select-as-flex-item-with-overflow.html
new file mode 100644
index 0000000..d53c530
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/select-as-flex-item-with-overflow.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>select as flex item with overflowing content</title>
+<link rel="help" href="http://www.w3.org/TR/css-flexbox-1/" />
+<link rel="stylesheet" href="/fonts/ahem.css">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+.flexBox {
+  display: flex;
+  flex-direction: column;
+  width: 100px;
+  overflow: scroll;
+}
+
+select {
+  font-family: Ahem;
+}
+</style>
+<p>Test passes if the flex box is not scrollable.</p>
+
+<div class=flexBox>
+  <select>
+      <option>this is a long long long long text</option>
+  </select>
+</div>
+<script>
+  test(function(t)
+    {
+      const flexBox = document.querySelector('.flexBox');
+      assert_less_than_equal(flexBox.scrollWidth, 100);
+    }, "select content (option) should not trigger scrollable overflow.");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-line-wrap-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-line-wrap-001-ref.html
new file mode 100644
index 0000000..f60a246c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-line-wrap-001-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<style>
+  div {
+    font: 16px/1 Ahem;
+    width: 100px;
+  }
+  span {
+    background-color: green;
+  }
+</style>
+<body>
+<div><span>one two three four five six seven eight nine ten</span></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-line-wrap-001.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-line-wrap-001.html
new file mode 100644
index 0000000..8a9bbeb1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-line-wrap-001.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Highlight API Test: no trailing whitespace painted at line wrap</title>
+<link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
+<link rel="match" href="custom-highlight-painting-line-wrap-001-ref.html">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<meta name="assert" value="Highlight backgrounds must not extend over trailing whitespace at line breaks.">
+<style>
+  div {
+    font: 16px/1 Ahem;
+    width: 100px;
+  }
+  ::highlight(test) {
+    background-color: green;
+  }
+</style>
+<body>
+<div id="d">one two three four five six seven eight nine ten</div>
+<script>
+  const text = document.getElementById("d").firstChild;
+  const r = new Range();
+  r.setStart(text, 0);
+  r.setEnd(text, text.length);
+  CSS.highlights.set("test", new Highlight(r));
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-single-axis-basic.html b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-single-axis-basic.html
new file mode 100644
index 0000000..b432af8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-single-axis-basic.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<title>Single-axis sticky constraints: Basic and Clip</title>
+<link rel="help" href="https://drafts.csswg.org/css-position-3/#sticky-pos">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  .scroller-y { overflow-y: scroll; overflow-x: visible; width: 100px; height: 100px; margin-bottom: 20px; }
+  .scroller-x { overflow-x: scroll; overflow-y: visible; width: 100px; height: 200px; }
+  .clip-y { overflow-x: scroll; overflow-y: clip; width: 100px; height: 200px; }
+  .sticky { position: sticky; top: 10px; left: 20px; width: 10px; height: 10px; background: green; }
+  .spacer { width: 400px; height: 400px; }
+</style>
+
+<!-- Standard independent scrollers. -->
+<div id="outer-basic" class="scroller-y">
+  <div id="inner-basic" class="scroller-x">
+    <div id="sticky-basic" class="sticky"></div>
+    <div class="spacer"></div>
+  </div>
+</div>
+
+<!-- Scroller with overflow: clip on one axis. -->
+<div id="outer-clip" class="scroller-y">
+  <div id="inner-clip" class="clip-y">
+    <div id="sticky-clip" class="sticky" style="left: 10px;"></div>
+    <div class="spacer"></div>
+  </div>
+</div>
+
+<script>
+test(() => {
+  let outer = document.getElementById("outer-basic");
+  let inner = document.getElementById("inner-basic");
+  let sticky = document.getElementById("sticky-basic");
+
+  // Scroll both axes. The sticky element tracks 'inner' for the X axis and 'outer' for the Y axis.
+  inner.scrollLeft = 50;
+  outer.scrollTop = 60;
+
+  let rOuter = outer.getBoundingClientRect();
+  let rSticky = sticky.getBoundingClientRect();
+
+  assert_equals(rSticky.left - rOuter.left, 20, "Left constraint applies to inner scroller");
+  assert_equals(rSticky.top - rOuter.top, 10, "Top constraint applies to outer scroller");
+}, "Constraints apply independently across nested single-axis scrollers");
+
+test(() => {
+  let outer = document.getElementById("outer-clip");
+  let inner = document.getElementById("inner-clip");
+  let sticky = document.getElementById("sticky-clip");
+
+  // Scroll both axes. Because 'inner' is overflow-y: clip, the sticky element skips it and attaches to 'outer' for the Y axis.
+  inner.scrollLeft = 50;
+  outer.scrollTop = 60;
+
+  let rOuter = outer.getBoundingClientRect();
+  let rSticky = sticky.getBoundingClientRect();
+
+  assert_equals(rSticky.left - rOuter.left, 10, "Left constraint applies to inner scroller");
+  assert_equals(rSticky.top - rOuter.top, 10, "Top constraint skips clip and applies to outer");
+}, "Constraints correctly skip axes that are overflow: clip");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-single-axis-dynamic.html b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-single-axis-dynamic.html
new file mode 100644
index 0000000..b8f753c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-single-axis-dynamic.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<title>Single-axis sticky constraints: Dynamic updates</title>
+<link rel="help" href="https://drafts.csswg.org/css-position-3/#sticky-pos">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #outer { overflow-y: scroll; overflow-x: visible; width: 100px; height: 100px; }
+  #inner { overflow-x: scroll; overflow-y: visible; width: 100px; height: 200px; }
+
+  /* Only 'top' is set initially. */
+  #sticky { position: sticky; top: 10px; width: 10px; height: 10px; background: green; }
+  .spacer { width: 400px; height: 400px; }
+</style>
+
+<div id="outer">
+  <div id="inner">
+    <div id="sticky"></div>
+    <div class="spacer"></div>
+  </div>
+</div>
+
+<script>
+test(() => {
+  let outer = document.getElementById("outer");
+  let inner = document.getElementById("inner");
+  let sticky = document.getElementById("sticky");
+
+  // Scroll the X axis. Without a 'left' constraint, the element scrolls with the content.
+  inner.scrollLeft = 50;
+
+  let rOuter = outer.getBoundingClientRect();
+  let rSticky = sticky.getBoundingClientRect();
+  assert_equals(rSticky.left - rOuter.left, -50, "Content scrolled left naturally (no constraint yet)");
+
+  // Scroll the Y axis. The 'top' constraint is active, so the element sticks.
+  outer.scrollTop = 60;
+
+  rOuter = outer.getBoundingClientRect();
+  rSticky = sticky.getBoundingClientRect();
+  assert_equals(rSticky.top - rOuter.top, 10, "Sticky top is applied after Y scroll");
+
+  // Dynamically add a 'left' constraint. This merges the new X constraint with the existing Y constraint.
+  sticky.style.left = "20px";
+
+  rOuter = outer.getBoundingClientRect();
+  rSticky = sticky.getBoundingClientRect();
+
+  assert_equals(rSticky.left - rOuter.left, 20, "Sticky left is merged and applied");
+  assert_equals(rSticky.top - rOuter.top, 10, "Sticky top is preserved after merge");
+
+  // Dynamically remove the 'top' constraint. This forces a layout and verifies
+  // that the stale Y-axis constraint is not leaked/merged into the new fragment.
+  sticky.style.top = "auto";
+
+  rOuter = outer.getBoundingClientRect();
+  rSticky = sticky.getBoundingClientRect();
+
+  assert_equals(rSticky.left - rOuter.left, 20, "Sticky left remains applied after Y is removed");
+  assert_equals(rSticky.top - rOuter.top, -60, "Sticky top is removed and element scrolls naturally");
+}, "Sequential scrolling and dynamic constraint updates merge and remove correctly");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-single-axis-nested.html b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-single-axis-nested.html
new file mode 100644
index 0000000..2c66bcfe
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-single-axis-nested.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<title>Single-axis sticky constraints: Nested elements</title>
+<link rel="help" href="https://drafts.csswg.org/css-position-3/#sticky-pos">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #outer { overflow-y: scroll; overflow-x: visible; width: 200px; height: 200px; }
+  #inner { overflow-x: scroll; overflow-y: visible; width: 200px; height: 1000px; }
+  #sticky-outer { position: sticky; top: 10px; left: 20px; width: 100px; height: 60px; background: lightblue; }
+  #sticky-inner { position: sticky; top: 25px; left: 40px; width: 50px; height: 20px; background: blue; }
+  .spacer { width: 1000px; height: 1000px; }
+</style>
+
+<div id="outer">
+  <div id="inner">
+    <div id="sticky-outer">
+      <div id="sticky-inner"></div>
+    </div>
+    <div class="spacer"></div>
+  </div>
+</div>
+
+<script>
+test(() => {
+  let outer = document.getElementById("outer");
+  let inner = document.getElementById("inner");
+  let stickyOuter = document.getElementById("sticky-outer");
+  let stickyInner = document.getElementById("sticky-inner");
+
+  // Scroll both axes to activate sticky offsets.
+  inner.scrollLeft = 50;
+  outer.scrollTop = 60;
+
+  let rOuter = outer.getBoundingClientRect();
+  let rStickyOuter = stickyOuter.getBoundingClientRect();
+  let rStickyInner = stickyInner.getBoundingClientRect();
+
+  // Verify the outer sticky element resolves against its own constraints.
+  assert_equals(rStickyOuter.left - rOuter.left, 20, "Outer sticky left");
+  assert_equals(rStickyOuter.top - rOuter.top, 10, "Outer sticky top");
+
+  // Verify the inner sticky element accumulates offsets from the outer sticky element.
+  assert_equals(rStickyInner.left - rOuter.left, 40, "Inner sticky left accumulates");
+  assert_equals(rStickyInner.top - rOuter.top, 25, "Inner sticky top accumulates");
+}, "Nested sticky elements accumulate offsets correctly on independent axes");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/attr-namespace-non-existing.html b/third_party/blink/web_tests/external/wpt/css/css-values/attr-namespace-non-existing.html
new file mode 100644
index 0000000..c1abc43
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/attr-namespace-non-existing.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#attr-notation">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+.a {
+   background: red;
+   height: 50px;
+   width: 100px;
+}
+
+.attr {
+  background: attr(foo|bar type(*), green);
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="a attr" bar="red"></div>
+<div class="a attr"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/attr-namespace-valid.xhtml b/third_party/blink/web_tests/external/wpt/css/css-values/attr-namespace-valid.xhtml
new file mode 100644
index 0000000..f5fdc18
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/attr-namespace-valid.xhtml
@@ -0,0 +1,24 @@
+<!DOCTYPE xhtml>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:color="http://www.example.com/">
+<head>
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#attr-notation"></link>
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht"></link>
+    <style>
+        @namespace color "http://www.example.com/";
+
+        div {
+            background-color: red;
+        }
+
+        .a {
+            width: 100px;
+            height: 100px;
+            background-color: attr(color|myAttr type(*), red);
+        }
+    </style>
+</head>
+<body>
+    <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+    <div class="a" color:myAttr="green"></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/attr-namespace-wildcard.html b/third_party/blink/web_tests/external/wpt/css/css-values/attr-namespace-wildcard.html
new file mode 100644
index 0000000..f9be5a7d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/attr-namespace-wildcard.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#attr-notation">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name="assert" content="Wildcard not supported in attr() function">
+<style>
+.a {
+   background: green;
+   height: 100px;
+   width: 100px;
+}
+
+.attr {
+  background: attr(*|bar type(*));
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="a attr" bar="red"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-computed-style.html b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-computed-style.html
index fe7b69a..03df479 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-computed-style.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-computed-style.html
@@ -85,6 +85,10 @@
       "value": "2px",
       "otherValues": ["4px"]
     },
+    "stroke-width": {
+      "value": "2px",
+      "otherValues": ["4px"]
+    },
     "width": {
       "value": "9px",
       "otherValues": ["19px"]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-stroke-width-ref.html b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-stroke-width-ref.html
new file mode 100644
index 0000000..14980cb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-stroke-width-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS zoom does not scale SVG stroke-width when defined on svg element - reference</title>
+<style>
+body { margin: 0; }
+</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 100 100"
+     fill="none" stroke="black" stroke-width="2">
+    <rect x="10" y="10" width="80" height="80"/>
+</svg>
+
+<div>
+    <svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 100 100"
+        fill="none" stroke="black" stroke-width="2">
+        <rect x="10" y="10" width="80" height="80"/>
+    </svg>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-stroke-width.html b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-stroke-width.html
new file mode 100644
index 0000000..ffc70cc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-stroke-width.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS zoom does not scale SVG stroke-width when defined on svg element</title>
+<link rel="help" href="https://drafts.csswg.org/css-viewport/#zoom-property">
+<link rel="help" href="https://drafts.fxtf.org/fill-stroke-3/#propdef-stroke-width">
+<link rel="match" href="svg-stroke-width-ref.html">
+<style>
+body { margin: 0; }
+.zoom { zoom: 2; }
+</style>
+
+<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"
+     fill="none" stroke="black" stroke-width="2" style="zoom: 2;">
+    <rect x="10" y="10" width="80" height="80"/>
+</svg>
+
+<div class="zoom">
+    <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"
+         fill="none" stroke="black" stroke-width="2">
+        <rect x="10" y="10" width="80" height="80"/>
+    </svg>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/forward-navigation/nested-focusgroup-is-item-of-parent.html b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/forward-navigation/nested-focusgroup-is-item-of-parent.html
index b1269b6..25912f34 100644
--- a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/forward-navigation/nested-focusgroup-is-item-of-parent.html
+++ b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/forward-navigation/nested-focusgroup-is-item-of-parent.html
@@ -14,11 +14,11 @@
 
 <button id=before tabindex=0>before</button>
 
-<div id=outer focusgroup="toolbar nomemory">
+<div id=outer focusgroup="toolbar no-memory">
   <button id=btn1>btn1</button>
   <button id=btn2>btn2</button>
   <button id=btn3>btn3</button>
-  <div id=inner focusgroup="toolbar nomemory" tabindex=0>
+  <div id=inner focusgroup="toolbar no-memory" tabindex=0>
     <button id=inner_btn1>inner btn1</button>
     <button id=inner_btn2>inner btn2</button>
     <button id=inner_btn3>inner btn3</button>
diff --git a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/arrow-key-handler-tab-escape.html b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/arrow-key-handler-tab-escape.html
index 1e4beb3f..fbe52722 100644
--- a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/arrow-key-handler-tab-escape.html
+++ b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/arrow-key-handler-tab-escape.html
@@ -14,7 +14,7 @@
 
 <div id=before tabindex=0>Before focusgroup</div>
 
-<div id=toolbar focusgroup="toolbar nomemory">
+<div id=toolbar focusgroup="toolbar no-memory">
   <button id=bold type="button">Bold</button>
   <!-- Native text input is a native arrow key handler for both axes. -->
   <input id=search type="text" value="Search" />
diff --git a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/basic-tab-behavior.html b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/basic-tab-behavior.html
index f530546..e090232 100644
--- a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/basic-tab-behavior.html
+++ b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/basic-tab-behavior.html
@@ -14,7 +14,7 @@
 <!-- Basic focusgroup for entry/exit testing -->
 <div id=before tabindex=0>Before focusgroup</div>
 
-<div id=focusgroup1 focusgroup="toolbar nomemory">
+<div id=focusgroup1 focusgroup="toolbar no-memory">
   <span id=item1 tabindex=0>Item 1</span>
   <span id=item2 tabindex=0>Item 2</span>
   <span id=item3 tabindex=0>Item 3</span>
diff --git a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/dynamic-changes.html b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/dynamic-changes.html
index c5b17847..77b92f5 100644
--- a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/dynamic-changes.html
+++ b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/dynamic-changes.html
@@ -14,7 +14,7 @@
 <!-- Test focusgroupstart changes during navigation -->
 <div id=before1 tabindex=0>Before test 1</div>
 
-<div id=fg1 focusgroup="toolbar nomemory">
+<div id=fg1 focusgroup="toolbar no-memory">
   <span id=item1 tabindex=0>Item 1</span>
   <span id=item2 tabindex=0>Item 2</span>
 </div>
@@ -24,7 +24,7 @@
 <!-- Test disabled element changes -->
 <div id=before2 tabindex=0>Before test 2</div>
 
-<div id=fg2 focusgroup="toolbar nomemory">
+<div id=fg2 focusgroup="toolbar no-memory">
   <button id=btn1>Button 1</button>
   <button id=btn2 disabled>Button 2</button>
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/focusgroup-segments.html b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/focusgroup-segments.html
index b72196c..c34aa9d 100644
--- a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/focusgroup-segments.html
+++ b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/focusgroup-segments.html
@@ -29,7 +29,7 @@
 <!-- Test complex nested opt-out -->
 <div id=before2 tabindex=0>Before complex</div>
 
-<div id=complex-focusgroup focusgroup="toolbar nomemory">
+<div id=complex-focusgroup focusgroup="toolbar no-memory">
   <button id=item1 focusgroupstart>Item 1 (priority)</button>
   <div id=nested-opt-out focusgroup="none">
     <button id=opted-out-1>Opted out 1</button>
diff --git a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/guaranteed-tab-stop-priority.html b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/guaranteed-tab-stop-priority.html
index 5bc85e63..2bbf50e 100644
--- a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/guaranteed-tab-stop-priority.html
+++ b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/guaranteed-tab-stop-priority.html
@@ -14,7 +14,7 @@
 <!-- Priority Tier 1: focusgroupstart attribute -->
 <div id=before1 tabindex=0>Before test 1</div>
 
-<div id=fg1 focusgroup="toolbar nomemory">
+<div id=fg1 focusgroup="toolbar no-memory">
   <span id=item1 tabindex=0>Item 1 (no priority)</span>
   <span id=item2 tabindex=0 focusgroupstart>Item 2 (has priority)</span>
   <span id=item3 tabindex=0>Item 3 (no priority)</span>
@@ -25,7 +25,7 @@
 <!-- Priority Tier 2: First element in tree order when no priority attribute -->
 <div id=before2 tabindex=0>Before test 2</div>
 
-<div id=fg2 focusgroup="toolbar nomemory">
+<div id=fg2 focusgroup="toolbar no-memory">
   <span id=first tabindex=0>First (no priority)</span>
   <span id=second tabindex=0>Second (no priority)</span>
 </div>
@@ -35,7 +35,7 @@
 <!-- Priority Tier 3: First element with priority attribute when multiple have it -->
 <div id=before3 tabindex=0>Before test 3</div>
 
-<div id=fg3 focusgroup="toolbar nomemory">
+<div id=fg3 focusgroup="toolbar no-memory">
   <span id=early1 tabindex=0 focusgroupstart>Early (has priority)</span>
   <span id=early2 tabindex=0 focusgroupstart>Later (has priority)</span>
 </div>
@@ -45,7 +45,7 @@
 <!-- Test with buttons (natively focusable) -->
 <div id=before4 tabindex=0>Before test 4</div>
 
-<div id=fg4 focusgroup="toolbar nomemory">
+<div id=fg4 focusgroup="toolbar no-memory">
   <button id=btn1>Button 1</button>
   <button id=btn2 focusgroupstart>Button 2 (has priority)</button>
   <button id=btn3>Button 3</button>
diff --git a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/memory-behavior.html b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/memory-behavior.html
index 269afcef..d04706e9 100644
--- a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/memory-behavior.html
+++ b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/memory-behavior.html
@@ -23,7 +23,7 @@
 <div id=between tabindex=0>Between focusgroups</div>
 
 <!-- Test focusgroup with no-memory -->
-<div id=no-memory-focusgroup focusgroup="toolbar nomemory">
+<div id=no-memory-focusgroup focusgroup="toolbar no-memory">
   <button id=no-memory-item1>Item 1</button>
   <button id=no-memory-item2 focusgroupstart>Item 2 (priority)</button>
   <button id=no-memory-item3>Item 3</button>
diff --git a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/nested-focusgroups.html b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/nested-focusgroups.html
index 6ca62a35..15ea05e 100644
--- a/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/nested-focusgroups.html
+++ b/third_party/blink/web_tests/external/wpt/html/interaction/focus/focusgroup/tentative/sequential-navigation/nested-focusgroups.html
@@ -16,7 +16,7 @@
 
 <div id=outer focusgroup="toolbar">
   <span id=outer1 tabindex=0 focusgroupstart>Outer 1 (priority)</span>
-  <div id=inner focusgroup="toolbar nomemory">
+  <div id=inner focusgroup="toolbar no-memory">
     <span id=inner1 tabindex=0 focusgroupstart>Inner 1 (priority)</span>
     <span id=inner2 tabindex=0>Inner 2</span>
   </div>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-explicit-size.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-explicit-size.html
index 71c77d2..970c1ebb 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-explicit-size.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-explicit-size.html
@@ -2,7 +2,7 @@
 <!-- Tests that select respects explicit size -->
 <link rel=author href="mailto:pkotwicz@chromium.org">
 <link rel="match" href="select-explicit-size-ref.html">
-
+<meta name="fuzzy" content="maxDifference=0-2; totalPixels=0-1">
 <style>
 select {
   width:400px;
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
index 10bf8f3..3cc299f 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
@@ -142,6 +142,7 @@
 PASS oldChildWindow.onclick is newChildWindow.onclick
 PASS oldChildWindow.onclose is newChildWindow.onclose
 PASS oldChildWindow.oncommand is newChildWindow.oncommand
+PASS oldChildWindow.oncomplete is newChildWindow.oncomplete
 PASS oldChildWindow.oncontentvisibilityautostatechange is newChildWindow.oncontentvisibilityautostatechange
 PASS oldChildWindow.oncontextlost is newChildWindow.oncontextlost
 PASS oldChildWindow.oncontextmenu is newChildWindow.oncontextmenu
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
index 936de40..92e6909 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
@@ -81,6 +81,7 @@
 PASS childWindow.onclick is null
 PASS childWindow.onclose is null
 PASS childWindow.oncommand is null
+PASS childWindow.oncomplete is null
 PASS childWindow.oncontentvisibilityautostatechange is null
 PASS childWindow.oncontextlost is null
 PASS childWindow.oncontextmenu is null
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
index c853ce4..757ea368 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
@@ -81,6 +81,7 @@
 PASS childWindow.onclick is null
 PASS childWindow.onclose is null
 PASS childWindow.oncommand is null
+PASS childWindow.oncomplete is null
 PASS childWindow.oncontentvisibilityautostatechange is null
 PASS childWindow.oncontextlost is null
 PASS childWindow.oncontextmenu is null
diff --git a/third_party/blink/web_tests/fast/forms/number/number-appearance-vertical.html b/third_party/blink/web_tests/fast/forms/number/number-appearance-vertical.html
index 9b87351..9db27c6 100644
--- a/third_party/blink/web_tests/fast/forms/number/number-appearance-vertical.html
+++ b/third_party/blink/web_tests/fast/forms/number/number-appearance-vertical.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 <meta name=fuzzy content="maxDifference=0-3; totalPixels=0-100">
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
 <style>
 div {
   display: inline-block;
@@ -45,6 +46,9 @@
 
 <script>
 if (window.internals) {
+  internals.setIsCursorVisible(document, true);
+  internals.settings.setPrimaryHoverType('hover');
+  internals.settings.setAvailableHoverTypes('hover');
   for (let input of document.querySelectorAll('input')) {
     internals.setPseudoClassState(input, ':hover', true);
   }
@@ -53,6 +57,14 @@
   // A spinbutton should be showing on the top (left)
   input.focus();
 }
+if (window.testRunner) {
+  testRunner.waitUntilDone();
+  runAfterLayoutAndPaint(function() {
+    runAfterLayoutAndPaint(function() {
+      testRunner.notifyDone();
+    });
+  });
+}
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/virtual/single-axis-scroll-containers/README.md b/third_party/blink/web_tests/virtual/single-axis-scroll-containers/README.md
new file mode 100644
index 0000000..a903fc2
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/single-axis-scroll-containers/README.md
@@ -0,0 +1,5 @@
+# SingleAxisScrollContainers
+
+Adds support for independent horizontal and vertical overflow behaviors. Elements can now scroll on one axis (e.g., overflow-x: scroll) while keeping the other axis clipped or visible (e.g., overflow-y: clip).
+
+This loosens the restriction that forced the non-scrolling axis into auto or hidden, allowing more flexible layouts.
diff --git a/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt b/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt
index 48660bda..3f959678 100644
--- a/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt
@@ -213,6 +213,7 @@
     property onclick
     property onclose
     property oncommand
+    property oncomplete
     property oncontentvisibilityautostatechange
     property oncontextlost
     property oncontextmenu
@@ -589,6 +590,13 @@
     property vAlign
     property width
 html element credential
+    property clientId
+    property configURL
+    property domainHint
+    property fields
+    property loginHint
+    property params
+    property type
 html element data
     property value
 html element datalist
@@ -912,6 +920,7 @@
 html element listing
     property width
 html element login
+    property credential
 html element main
 html element map
     property areas
@@ -1543,6 +1552,7 @@
     property onclick
     property onclose
     property oncommand
+    property oncomplete
     property oncontentvisibilityautostatechange
     property oncontextlost
     property oncontextmenu
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index c2edee1a..db45087 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -2043,6 +2043,7 @@
     getter onclick
     getter onclose
     getter oncommand
+    getter oncomplete
     getter oncontentvisibilityautostatechange
     getter oncontextlost
     getter oncontextmenu
@@ -2269,6 +2270,7 @@
     setter onclick
     setter onclose
     setter oncommand
+    setter oncomplete
     setter oncontentvisibilityautostatechange
     setter oncontextlost
     setter oncontextmenu
@@ -3853,7 +3855,21 @@
     method namedItem
 interface HTMLCredentialElement : HTMLElement
     attribute @@toStringTag
+    getter clientId
+    getter configURL
+    getter domainHint
+    getter fields
+    getter loginHint
+    getter params
+    getter type
     method constructor
+    setter clientId
+    setter configURL
+    setter domainHint
+    setter fields
+    setter loginHint
+    setter params
+    setter type
 interface HTMLDListElement : HTMLElement
     attribute @@toStringTag
     getter compact
@@ -3945,6 +3961,7 @@
     getter onclick
     getter onclose
     getter oncommand
+    getter oncomplete
     getter oncontentvisibilityautostatechange
     getter oncontextlost
     getter oncontextmenu
@@ -4084,6 +4101,7 @@
     setter onclick
     setter onclose
     setter oncommand
+    setter oncomplete
     setter oncontentvisibilityautostatechange
     setter oncontextlost
     setter oncontextmenu
@@ -4703,6 +4721,7 @@
     setter type
 interface HTMLLoginElement : HTMLElement
     attribute @@toStringTag
+    getter credential
     method constructor
 interface HTMLMapElement : HTMLElement
     attribute @@toStringTag
@@ -6086,6 +6105,7 @@
     getter onclick
     getter onclose
     getter oncommand
+    getter oncomplete
     getter oncontentvisibilityautostatechange
     getter oncontextlost
     getter oncontextmenu
@@ -6200,6 +6220,7 @@
     setter onclick
     setter onclose
     setter oncommand
+    setter oncomplete
     setter oncontentvisibilityautostatechange
     setter oncontextlost
     setter oncontextmenu
@@ -8655,6 +8676,7 @@
     getter onclick
     getter onclose
     getter oncommand
+    getter oncomplete
     getter oncontentvisibilityautostatechange
     getter oncontextlost
     getter oncontextmenu
@@ -8771,6 +8793,7 @@
     setter onclick
     setter onclose
     setter oncommand
+    setter oncomplete
     setter oncontentvisibilityautostatechange
     setter oncontextlost
     setter oncontextmenu
@@ -13517,6 +13540,7 @@
     getter onclick
     getter onclose
     getter oncommand
+    getter oncomplete
     getter oncontentvisibilityautostatechange
     getter oncontextlost
     getter oncontextmenu
@@ -13751,6 +13775,7 @@
     setter onclick
     setter onclose
     setter oncommand
+    setter oncomplete
     setter oncontentvisibilityautostatechange
     setter oncontextlost
     setter oncontextmenu
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/anchor-positioning.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/anchor-positioning.html
index 17f91517..8a28be7 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/anchor-positioning.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/anchor-positioning.html
@@ -1,7 +1,9 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage with out-of-canvas anchored element</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="match" href="anchor-positioning-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
   #canvas {
     width: 200px;
@@ -37,6 +39,8 @@
   var context = canvas.getContext('2d');
   let matrix = context.drawElementImage(anchor, 50, 50);
   anchor.style.transform = matrix.toString();
+  takeScreenshot();
 }
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 </script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/basic-rect-zoom.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/basic-rect-zoom.html
index 5b640ad..39055a9 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/basic-rect-zoom.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/basic-rect-zoom.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: green rect when zoomed</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="basic-rect-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
 body {
   zoom: 200%;
@@ -31,9 +32,10 @@
   canvas.width = rectangle.width * devicePixelRatio;
   canvas.height = rectangle.height * devicePixelRatio;
   canvas.getContext("2d").drawElementImage(child, 20 * devicePixelRatio, 30 * devicePixelRatio);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/basic-rect.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/basic-rect.html
index 3a980fb..43ee72b 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/basic-rect.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/basic-rect.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: green rect</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:vmpstr@chromium.org">
 <link rel="match" href="basic-rect-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
 #child {
   width: 100px;
@@ -22,9 +23,10 @@
 <script>
 function runTest() {
   canvas.getContext("2d").drawElementImage(child, 20, 30);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/broken-image-ref.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/broken-image-ref.html
new file mode 100644
index 0000000..5e4b6203
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/broken-image-ref.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Canvas.drawElementImage: green rect with broken image (ref)</title>
+<link rel="help" href="https://github.com/WICG/html-in-canvas">
+<link rel="author" href="mailto:szager@chromium.org">
+<style>
+#child {
+  will-change: transform;
+  width: 100px;
+  height: 100px;
+  background: green;
+
+  position: relative;
+  left: 20px;
+  top: 30px;
+}
+#canvas {
+  width: 200px;
+  height: 200px;
+  background: grey;
+}
+img {
+    width: 100px;
+    height: 100px;
+}
+</style>
+
+<div id=canvas>
+  <div id=child>
+    <img id=image src="resoces/red-green-animated.gif"/>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/clips-overflow.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/clips-overflow.html
index 882b76a..f2451150 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/clips-overflow.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/clips-overflow.html
@@ -29,10 +29,10 @@
 <script>
 function runTest() {
   canvas.getContext("2d").drawElementImage(child, 20, 30);
-  requestAnimationFrame(takeScreenshot);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/compositing-op-basic.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/compositing-op-basic.html
index 09ceea01..e1865ca5 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/compositing-op-basic.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/compositing-op-basic.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: compositing op green rect</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="compositing-op-basic-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
 #child {
   width: 100px;
@@ -26,9 +27,10 @@
   context.fillRect(70, 80, 110, 100);
   context.globalCompositeOperation = "source-atop";
   context.drawElementImage(child, 20, 30);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/compositing-op-non-opaque-element.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/compositing-op-non-opaque-element.html
index be6190e..a4ff6fd 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/compositing-op-non-opaque-element.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/compositing-op-non-opaque-element.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: non opaque element with compositing op</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="compositing-op-non-opaque-element-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
 #child {
   width: 100px;
@@ -37,9 +38,10 @@
   context.fillRect(10, 10, 180, 180);
   context.globalCompositeOperation = "destination-out";
   context.drawElementImage(child, 20, 30);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/containment.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/containment.html
index 76946b0..91d8d83 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/containment.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/containment.html
@@ -1,5 +1,4 @@
 <!DOCTYPE html>
-<html class=reftest-wait>
 <title>Canvas.drawElementImage: styles for children of canvas</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:chrishtr@chromium.org">
@@ -36,5 +35,5 @@
     assert_equals(getComputedStyle(grandchild1)[a], grandchild_asserts[a]);
     assert_equals(getComputedStyle(grandchild2)[a], grandchild_asserts[a]);
   }
- }, 'canvas child containment');
+}, 'canvas child containment');
 </script>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/crash-on-load.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/crash-on-load.html
index 8aa1930..9ed71fc 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/crash-on-load.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/crash-on-load.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: drawElementImage with missing images doesn't crash</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
-<link rel="match" href="basic-rect-ref.html">
+<link rel="match" href="broken-image-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
 #child {
   width: 100px;
@@ -26,7 +27,8 @@
 <script>
 function runTest() {
   canvas.getContext("2d").drawElementImage(child, 20, 30);
+  takeScreenshot();
 }
-runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 </script>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-backdrop-filter-and-mask.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-backdrop-filter-and-mask.html
index dd25700..7b66227 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-backdrop-filter-and-mask.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-backdrop-filter-and-mask.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <head>
   <title>drawElementImage paints CSS backdrop filter with mask image</title>
   <link rel="help" href="https://github.com/WICG/html-in-canvas">
   <link rel="author" href="mailto:schenney@chromium.org">
   <link rel="match" href="css-backdrop-filter-and-mask-ref.html">
   <meta name="assert" value="A div with a CSS backdrop-filter and mask paints correctly.">
+  <script src="/common/reftest-wait.js"></script>
   <style>
     canvas {
       background: red;
@@ -30,9 +32,12 @@
   </canvas>
 
   <script>
-    window.onload = () => {
+    function runTest() {
       var context = canvas.getContext("2d");
       context.drawElementImage(child, 0, 0);
+      takeScreenshot();
     }
+    onload = () => requestAnimationFrame(() => setTimeout(runTest));
   </script>
-</body>
\ No newline at end of file
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-backdrop-filter-on-div.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-backdrop-filter-on-div.html
index 3066a18..24cb54c 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-backdrop-filter-on-div.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-backdrop-filter-on-div.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <head>
   <title>drawElementImage paints CSS backdrop filter within bounds</title>
   <link rel="help" href="https://github.com/WICG/html-in-canvas">
   <link rel="author" href="mailto:schenney@chromium.org">
   <link rel="match" href="css-backdrop-filter-on-div-ref.html">
   <meta name="assert" value="A div with a CSS backdrop-filter paints with the filter in the correct area.">
+  <script src="/common/reftest-wait.js"></script>
   <style>
     canvas {
       background: red;
@@ -33,9 +35,12 @@
   </canvas>
 
   <script>
-    window.onload = () => {
+    function runTest() {
       var context = canvas.getContext("2d");
       context.drawElementImage(child, 0, 0);
+      takeScreenshot();
     }
+    onload = () => requestAnimationFrame(() => setTimeout(runTest));
   </script>
-</body>
\ No newline at end of file
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-blur-filter-on-div.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-blur-filter-on-div.html
index b843c85..9fc2185c 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-blur-filter-on-div.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-blur-filter-on-div.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <head>
   <title>drawElementImage paints CSS blur filters</title>
   <link rel="help" href="https://github.com/WICG/html-in-canvas">
   <link rel="match" href="css-blur-filter-on-div-ref.html">
   <meta name="assert" value="A div with a CSS blur filter paints with the filter.">
+  <script src="/common/reftest-wait.js"></script>
   <style>
     div {
       width: 100px;
@@ -19,9 +21,12 @@
   </canvas>
 
   <script>
-    window.onload = () => {
+    function runTest() {
       var context = canvas.getContext("2d");
       context.drawElementImage(child, 0, 0);
+      takeScreenshot();
     }
-  </script>
+    onload = () => requestAnimationFrame(() => setTimeout(runTest));
+ </script>
 </body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-clip-path-on-div.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-clip-path-on-div.html
index cca8fee..3910bdb3 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-clip-path-on-div.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-clip-path-on-div.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <head>
   <title>drawElementImage paints CSS clip-path</title>
   <link rel="help" href="https://github.com/WICG/html-in-canvas">
   <link rel="match" href="css-clip-path-on-div-ref.html">
   <meta name="assert" value="A div with a CSS clip-path paints clipped.">
   <meta name="fuzzy" content="maxDifference=0-1;totalPixels=0-2">
+  <script src="/common/reftest-wait.js"></script>
   <style>
     div {
       width: 100px;
@@ -20,9 +22,12 @@
   </canvas>
 
   <script>
-    window.onload = () => {
+    function runTest() {
       var context = canvas.getContext("2d");
       context.drawElementImage(child, 0, 0);
+      takeScreenshot();
     }
+    onload = () => requestAnimationFrame(() => setTimeout(runTest));
   </script>
 </body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-filter-on-div.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-filter-on-div.html
index a0b9bb1..942424b 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-filter-on-div.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-filter-on-div.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <head>
   <title>drawElementImage paints CSS filters</title>
   <link rel="help" href="https://github.com/WICG/html-in-canvas">
   <link rel="author" href="mailto:schenney@chromium.org">
   <link rel="match" href="css-filter-on-div-ref.html">
   <meta name="assert" value="A div with a CSS grayscale filter paints with the filter.">
+  <script src="/common/reftest-wait.js"></script>
   <style>
     canvas {
       background: red;
@@ -23,9 +25,12 @@
   </canvas>
 
   <script>
-    window.onload = () => {
+    function runTest() {
       var context = canvas.getContext("2d");
       context.drawElementImage(child, 0, 0);
+      takeScreenshot();
     }
+    onload = () => requestAnimationFrame(() => setTimeout(runTest));
   </script>
 </body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-filter-reference-on-div.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-filter-reference-on-div.html
index 3f6d8f3..d988e73 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-filter-reference-on-div.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-filter-reference-on-div.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <head>
   <title>drawElementImage paints CSS reference filters</title>
   <link rel="help" href="https://github.com/WICG/html-in-canvas">
   <link rel="author" href="mailto:schenney@chromium.org">
   <link rel="match" href="css-filter-reference-on-div-ref.html">
   <meta name="assert" value="A div with a CSS filter referencing SVG paints with the filter.">
+  <script src="/common/reftest-wait.js"></script>
   <style>
     canvas {
       background: red;
@@ -29,9 +31,12 @@
   </svg>
 
   <script>
-    window.onload = () => {
-        var context = canvas.getContext("2d");
-        context.drawElementImage(child, 0, 0);
+    function runTest() {
+      var context = canvas.getContext("2d");
+      context.drawElementImage(child, 0, 0);
+      takeScreenshot();
     }
+    onload = () => requestAnimationFrame(() => setTimeout(runTest));
   </script>
 </body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-mask-on-div.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-mask-on-div.html
index 15d3fd00..061ab85 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-mask-on-div.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-mask-on-div.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <head>
   <title>drawElementImage paints CSS mask</title>
   <link rel="help" href="https://github.com/WICG/html-in-canvas">
   <link rel="match" href="css-mask-on-div-ref.html">
   <meta name="assert" value="A div with mask paints with the mask.">
   <meta name="fuzzy" content="maxDifference=0-1;totalPixels=0-10000">
+  <script src="/common/reftest-wait.js"></script>
   <style>
     div {
       width: 100px;
@@ -20,9 +22,12 @@
   </canvas>
 
   <script>
-    window.onload = () => {
+    function runTest() {
       var context = canvas.getContext("2d");
       context.drawElementImage(child, 0, 0);
+      takeScreenshot();
     }
+    onload = () => requestAnimationFrame(() => setTimeout(runTest));
   </script>
 </body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-opacity-on-div.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-opacity-on-div.html
index ec578f4..8e301abd 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-opacity-on-div.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-opacity-on-div.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <head>
   <title>drawElementImage paints CSS opacity</title>
   <link rel="help" href="https://github.com/WICG/html-in-canvas">
   <link rel="match" href="css-opacity-on-div-ref.html">
   <meta name="assert" value="A div with opacity paints with the opacity.">
   <meta name="fuzzy" content="maxDifference=0-1;totalPixels=0-10000">
+  <script src="/common/reftest-wait.js"></script>
   <style>
     div {
       width: 100px;
@@ -20,9 +22,12 @@
   </canvas>
 
   <script>
-    window.onload = () => {
+    function runTest() {
       var context = canvas.getContext("2d");
       context.drawElementImage(child, 0, 0);
+      takeScreenshot();
     }
+    onload = () => requestAnimationFrame(() => setTimeout(runTest));
   </script>
 </body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-transform-on-div.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-transform-on-div.html
index 0b9cd2db..8dab736 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-transform-on-div.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/css-transform-on-div.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <head>
   <title>drawElementImage ignores CSS transforms</title>
   <link rel="help" href="https://github.com/WICG/html-in-canvas">
   <link rel="author" href="mailto:schenney@chromium.org">
   <link rel="match" href="css-transform-on-div-ref.html">
   <meta name="assert" value="A div with a CSS transform paints without the transform.">
+  <script src="/common/reftest-wait.js"></script>
   <style>
     canvas {
       background: black;
@@ -23,9 +25,12 @@
   </canvas>
 
   <script>
-    window.onload = () => {
+    function runTest() {
       var context = canvas.getContext("2d");
       context.drawElementImage(child, 0, 0);
+      takeScreenshot();
     }
+    window.onload = () => requestAnimationFrame(() => setTimeout(runTest));
   </script>
 </body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/draw-element-image-get-image-data.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/draw-element-image-get-image-data.html
index 0384433..87e15949 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/draw-element-image-get-image-data.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/draw-element-image-get-image-data.html
@@ -20,6 +20,8 @@
 
 <script>
   promise_test(async () => {
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     let ctx = canvas.getContext('2d');
     ctx.fillStyle = 'rgb(3, 4, 5)';
     ctx.fillRect(0, 0, 200, 200);
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/draw-element-image-returned-matrix.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/draw-element-image-returned-matrix.html
index 7484c257..4954fcc 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/draw-element-image-returned-matrix.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/draw-element-image-returned-matrix.html
@@ -1,56 +1,66 @@
 <!DOCTYPE html>
-<html>
 <title>drawElementImage should return a matrix transforming the element's layout location to the drawn location</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <canvas id="canvas" style="width: 100px; height: 100px;" width="100" height="100" layoutsubtree>
-  <div id="element" style="width: 10px; height: 10px;"></div>
+  <div id="element" style="width: 10px; height: 10px;">hello</div>
 </canvas>
 <script>
-  test(() => {
+async function waitOneFrame() {
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
+}
+
+function runTest() {
+  promise_test(async function() {
+    await waitOneFrame();
     let draw_transform = canvas.getContext('2d').drawElementImage(element, 0, 0);
     let expected = new DOMMatrix();
     assert_array_equals(draw_transform.toFloat64Array(), expected.toFloat64Array());
   }, 'Draw transform at the origin should be an identity matrix');
 
-  test(() => {
+  promise_test(async function() {
+    await waitOneFrame();
     let draw_transform = canvas.getContext('2d').drawElementImage(element, 17, -9);
     let expected = new DOMMatrix().translateSelf(17, -9);
     assert_array_equals(draw_transform.toFloat64Array(), expected.toFloat64Array());
   }, 'Draw transform should account for x and y');
 
-  test((t) => {
+  promise_test(async function(t) {
     t.add_cleanup(() => {
       element.style.transformOrigin = '';
     });
     element.style.transformOrigin = '0 0';
+    await waitOneFrame();
     let draw_transform = canvas.getContext('2d').drawElementImage(element, 0, 0, 20, 5);
     let expected = new DOMMatrix().scaleSelf(2, 0.5);
     assert_array_equals(draw_transform.toFloat64Array(), expected.toFloat64Array());
   }, 'Draw transform should account for width and height scale');
 
-  test((t) => {
+  promise_test(async function(t) {
     t.add_cleanup(() => {
       element.style.transform = '';
     });
     element.style.transform = 'rotate(45deg)';
+    await waitOneFrame();
     let draw_transform = canvas.getContext('2d').drawElementImage(element, 0, 0);
     let expected = new DOMMatrix();
     assert_array_equals(draw_transform.toFloat64Array(), expected.toFloat64Array());
   }, 'Draw transform should not include transform on the element');
 
-  test((t) => {
+  promise_test(async function(t) {
     t.add_cleanup(() => {
       canvas.getContext('2d').reset();
     });
     canvas.getContext('2d').translate(42, 17);
+    await waitOneFrame();
     let draw_transform = canvas.getContext('2d').drawElementImage(element, 0, 0);
     let expected = new DOMMatrix().translateSelf(42, 17);
     assert_array_equals(draw_transform.toFloat64Array(), expected.toFloat64Array());
   }, 'Draw transform should account for the current transform matrix with translation');
 
-  test((t) => {
+  promise_test(async function(t) {
     t.add_cleanup(() => {
       canvas.style.zoom = '';
     });
@@ -60,24 +70,26 @@
     assert_array_equals(draw_transform.toFloat64Array(), expected.toFloat64Array());
   }, 'Draw transform should not be affected by zoom on the canvas');
 
-  test((t) => {
+  promise_test(async function(t) {
     t.add_cleanup(() => {
       element.style.transformOrigin = '';
       canvas.getContext('2d').reset();
     });
     element.style.transformOrigin = '0 0';
+    await waitOneFrame();
     canvas.getContext('2d').rotate(Math.PI / 4);
     let draw_transform = canvas.getContext('2d').drawElementImage(element, 0, 0);
     let expected = new DOMMatrix().rotateSelf(45);
     assert_array_approx_equals(draw_transform.toFloat64Array(), expected.toFloat64Array(), 0.001);
   }, 'Draw transform should account for the current transform matrix with rotation');
 
-  test((t) => {
+  promise_test(async function(t) {
     t.add_cleanup(() => {
       element.style.transformOrigin = '';
       canvas.getContext('2d').reset();
     });
     element.style.transformOrigin = '5px 5px';
+    await waitOneFrame();
     canvas.getContext('2d').rotate(Math.PI / 4);
     let draw_transform = canvas.getContext('2d').drawElementImage(element, 0, 0);
     let expected = new DOMMatrix()
@@ -87,7 +99,7 @@
     assert_array_approx_equals(draw_transform.toFloat64Array(), expected.toFloat64Array(), 0.001);
   }, 'Draw transform should account for transform-origin');
 
-  test((t) => {
+  promise_test(async function(t) {
     t.add_cleanup(() => {
       canvas.width = 100;
       canvas.height = 100;
@@ -99,7 +111,7 @@
     assert_array_equals(draw_transform.toFloat64Array(), expected.toFloat64Array());
   }, 'Draw transform should account for canvas pixel scale');
 
-  test((t) => {
+  promise_test(async function(t) {
     t.add_cleanup(() => {
       canvas.width = 100;
       canvas.height = 100;
@@ -113,6 +125,7 @@
     canvas.getContext('2d').translate(42, 17);
     canvas.getContext('2d').rotate(Math.PI / 4);
     element.style.transformOrigin = '5px 5px';
+    element.style.transformOrigin = '5px 5px';
     let draw_transform = canvas.getContext('2d').drawElementImage(element, 13, -9);
     let expected = new DOMMatrix()
       .translateSelf(-5, -5)
@@ -124,4 +137,7 @@
       .translateSelf(5, 5);
     assert_array_approx_equals(draw_transform.toFloat64Array(), expected.toFloat64Array(), 0.001);
   }, 'Draw transform should account for canvas pixel scale, zoom, transform-origin, CTM, and x and y');
+}
+
+window.onload = runTest;
 </script>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/draw-element-image-scale-variant.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/draw-element-image-scale-variant.html
index da654e70..42f5abc 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/draw-element-image-scale-variant.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/draw-element-image-scale-variant.html
@@ -26,7 +26,7 @@
   requestAnimationFrame(takeScreenshot);
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/drawing-display-none-fails.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/drawing-display-none-fails.html
index ed773c6e..e030cfb 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/drawing-display-none-fails.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/drawing-display-none-fails.html
@@ -13,9 +13,11 @@
 </canvas>
 
 <script>
-  test(function() {
+  promise_test(async function() {
     canvas_2d_div.getBoundingClientRect();
     canvas_2d_div.style.display = 'none';
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     const context_2d = canvas_2d.getContext('2d');
     assert_throws_js(
       TypeError,
@@ -24,9 +26,11 @@
     );
   }, 'drawElementImage with display:none element');
 
-  test(function() {
+  promise_test(async function() {
     canvas_3d_div.getBoundingClientRect();
     canvas_3d_div.style.display = 'none';
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     const context_gl = canvas_3d.getContext('webgl2');
     const tex = context_gl.createTexture();
     context_gl.bindTexture(context_gl.TEXTURE_2D, tex);
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/element-shadow.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/element-shadow.html
index 51d3345a..e1b5b01 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/element-shadow.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/element-shadow.html
@@ -26,7 +26,7 @@
   requestAnimationFrame(takeScreenshot);
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/error-conditions.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/error-conditions.html
index 62e87d4..c51f578 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/error-conditions.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/error-conditions.html
@@ -29,6 +29,9 @@
 
 <script>
 promise_test(async () => {
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
+
   assert_throws_js(TypeError,
     () => canvas.getContext("2d").drawElementImage(grandchild, 20, 30),
     "Can't draw non-direct children.");
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/filtered-basic.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/filtered-basic.html
index 27030f5a..1157dcc 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/filtered-basic.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/filtered-basic.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: filtered green rect</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="filtered-basic-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
 #child {
   width: 100px;
@@ -24,9 +25,10 @@
   var context = canvas.getContext("2d");
   context.filter = "blur(5px)";
   context.drawElementImage(child, 20, 30);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/fireOnEveryPaint-css-animation.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/fireOnEveryPaint-css-animation.html
index 9dd1364e..214a6a7 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/fireOnEveryPaint-css-animation.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/fireOnEveryPaint-css-animation.html
@@ -43,6 +43,8 @@
         canvas.getContext('2d').drawElementImage(child, 0, 0);
       }
     });
+    // Wait a frame to cache initial paint
+    await waitForOneFrame();
     resizeObserver.observe(canvas, {fireOnEveryPaint: true});
 
     // 1. Wait a frame and assert that the initial observation occurred.
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/get-element-transform.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/get-element-transform.html
index 7b67281..77faeb8 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/get-element-transform.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/get-element-transform.html
@@ -119,3 +119,4 @@
     assert_array_approx_equals(element_transform.toFloat64Array(), expected.toFloat64Array(), 0.001);
   }, 'Element transform should account for canvas pixel scale, transform-origin, and a complex draw transform');
 </script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/global-alpha-basic.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/global-alpha-basic.html
index 034d507..438a448 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/global-alpha-basic.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/global-alpha-basic.html
@@ -1,10 +1,11 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: green rect with alpha</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="global-alpha-basic-ref.html">
 <meta name=fuzzy content="1;2500">
+<script src="/common/reftest-wait.js"></script>
 <style>
 #child {
   width: 100px;
@@ -27,9 +28,10 @@
   context.fillRect(70, 80, 110, 100);
   context.globalAlpha = 0.5;
   context.drawElementImage(child, 20, 30);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/hit-test/selection.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/hit-test/selection.html
index 37569b5..3523132f8 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/hit-test/selection.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/hit-test/selection.html
@@ -19,10 +19,12 @@
 </canvas>
 
 <script>
-  const ctx = canvas.getContext('2d');
-  ctx.drawElementImage(target, 0, 0);
-
   promise_test(async () => {
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
+    const ctx = canvas.getContext('2d');
+    ctx.drawElementImage(target, 0, 0);
+
     let doubleClickEventFired = false;
     canvas.addEventListener('dblclick', () => {
       doubleClickEventFired = true;
@@ -47,3 +49,4 @@
         '#target text should be selected by double-click.');
   }, 'Double-clicking on canvas should be able to modify selection.');
 </script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/iframe-local.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/iframe-local.html
index 5e50885..a904ac03 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/iframe-local.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/iframe-local.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: same-origin iframe test</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="basic-rect-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
 #child {
   width: 100px;
@@ -30,9 +31,10 @@
 <script>
 function runTest() {
   canvas.getContext("2d").drawElementImage(child, 20, 30);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/ink-overflow.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/ink-overflow.html
index c1bff0c..cec6f5d 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/ink-overflow.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/ink-overflow.html
@@ -68,6 +68,7 @@
 
 Promise.all(Array.from(document.querySelectorAll("canvas")).map(async cvs => {
   await resizeToPixelGrid(cvs);
+  await new Promise(setTimeout);
 
   const target = cvs.firstElementChild;
   const targetStyle = getComputedStyle(target);
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/intersection-observer-visibility.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/intersection-observer-visibility.html
index 2ce75d2..c99286a 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/intersection-observer-visibility.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/intersection-observer-visibility.html
@@ -10,6 +10,8 @@
 
 <script>
 promise_test(async function(t) {
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
   canvas.getContext('2d').drawElementImage(target, 0, 0);
 
   await new Promise(r => requestAnimationFrame(r));
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/moved-nested-canvas.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/moved-nested-canvas.html
index 2bfa117..180ef74 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/moved-nested-canvas.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/moved-nested-canvas.html
@@ -1,7 +1,9 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <title>drawElementImage should support rendering a nested canvas that was moved under the canvas</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="match" href="nested-canvas-ref.html">
+<script src="/common/reftest-wait.js"></script>
 
 <canvas id="canvas_a" layoutsubtree="true" width="100" height="100">
   <div id="div" style="width: 100px; height: 100px; background: radial-gradient(lightgreen, green);"></div>
@@ -9,7 +11,16 @@
 <canvas id="canvas_b" layoutsubtree="true" width="100" height="100" style="background: red;">
 </canvas>
 <script>
+async function runTest() {
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
   canvas_a.getContext('2d').drawElementImage(div, 0, 0, 100, 100);
   canvas_b.appendChild(canvas_a);
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
   canvas_b.getContext('2d').drawElementImage(canvas_a, 0, 0, 100, 100);
+  takeScreenshot();
+}
+onload = () => runTest();
 </script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/nested-canvas.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/nested-canvas.html
index c325aa78..5d95362 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/nested-canvas.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/nested-canvas.html
@@ -1,7 +1,9 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <title>drawElementImage should support rendering a nested canvas</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="match" href="nested-canvas-ref.html">
+<script src="/common/reftest-wait.js"></script>
 
 <canvas id="canvas" layoutsubtree="true" width="100" height="100" style="background: red;">
   <canvas id="nested_canvas" layoutsubtree="true" width="100" height="100">
@@ -9,6 +11,15 @@
   </canvas>
 </canvas>
 <script>
+async function runTest() {
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
   nested_canvas.getContext('2d').drawElementImage(inner_div, 0, 0, 100, 100);
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
   canvas.getContext('2d').drawElementImage(nested_canvas, 0, 0, 100, 100);
+  takeScreenshot();
+}
+onload = () => runTest();
 </script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/nested-div-canvas.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/nested-div-canvas.html
index fa893e7b..da66600 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/nested-div-canvas.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/nested-div-canvas.html
@@ -1,7 +1,9 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <title>drawElementImage should support rendering a div containing a canvas, all under a canvas</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="match" href="nested-canvas-ref.html">
+<script src="/common/reftest-wait.js"></script>
 
 <canvas id="canvas" layoutsubtree="true" width="100" height="100" style="background: red;">
   <div id="nested_div" style="width: 100px; height: 100px;">
@@ -11,6 +13,15 @@
   </div>
 </canvas>
 <script>
+async function runTest() {
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
   nested_canvas.getContext('2d').drawElementImage(inner_div, 0, 0, 100, 100);
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
   canvas.getContext('2d').drawElementImage(nested_div, 0, 0, 100, 100);
+  takeScreenshot();
+}
+onload = () => runTest();
 </script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/nested-webgl-canvas.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/nested-webgl-canvas.html
index 2f7f495..f5cdf69 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/nested-webgl-canvas.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/nested-webgl-canvas.html
@@ -1,7 +1,9 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <title>drawElementImage should support rendering a nested webgl canvas</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="match" href="nested-canvas-ref.html">
+<script src="/common/reftest-wait.js"></script>
 
 <canvas id="webgl_canvas" layoutsubtree="true" width="100" height="100" style="background: red;">
   <canvas id="nested_webgl_canvas" layoutsubtree="true" width="100" height="100">
@@ -10,7 +12,7 @@
 </canvas>
 <script>
   function drawWebGLElementImage(canvas, child) {
-    const gl = canvas.getContext('webgl2');
+    const gl = canvas.getContext('webgl2', {preserveDrawingBuffer:true});
     const vsSrc = `#version 300 es
       in vec2 a_pos;
       in vec2 a_uv;
@@ -72,6 +74,15 @@
     gl.drawArrays(gl.TRIANGLES, 0, 6);
   }
 
+async function runTest() {
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
   drawWebGLElementImage(nested_webgl_canvas, inner_div);
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
   drawWebGLElementImage(webgl_canvas, nested_webgl_canvas);
+  takeScreenshot();
+}
+onload = () => runTest();
 </script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/non-opaque-element.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/non-opaque-element.html
index f8371a45..f4c6ca0 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/non-opaque-element.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/non-opaque-element.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: div with transparent background</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="non-opaque-element-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
 #child {
   width: 100px;
@@ -33,9 +34,10 @@
 <script>
 function runTest() {
   canvas.getContext("2d").drawElementImage(child, 20, 30);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/percent-sizing.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/percent-sizing.html
index fdf4dc72..b1e59f48 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/percent-sizing.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/percent-sizing.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: percentage sizing of canvas children</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="basic-rect-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
 #child {
   width: 50%;
@@ -31,9 +32,10 @@
 <script>
 function runTest() {
   canvas.getContext("2d").drawElementImage(child, 20, 30);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/background-png-images-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/background-png-images-ignored.https.sub.html
index 2904503..a890121 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/background-png-images-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/background-png-images-ignored.https.sub.html
@@ -37,24 +37,24 @@
   </canvas>
 
   <script>
-    window.onload = () => {
-      test(function(t) {
-        var context = canvas.getContext("2d");
-        context.drawElementImage(child, 0, 0);
+window.onload = () => {
+  promise_test(async function(t) {
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
+    var context = canvas.getContext("2d");
+    context.drawElementImage(child, 0, 0);
 
-        pixel = context.getImageData(50, 50, 1, 1).data;
-        assert_equals(pixel[0], 255, "Same origin should draw");
-        assert_equals(pixel[1], 0, "Same origin should draw");
-        assert_equals(pixel[2], 0, "Same origin should draw");
+    pixel = context.getImageData(50, 50, 1, 1).data;
+    assert_equals(pixel[0], 255, "Same origin should draw");
+    assert_equals(pixel[1], 0, "Same origin should draw");
+    assert_equals(pixel[2], 0, "Same origin should draw");
 
-        pixel = context.getImageData(50, 150, 1, 1).data;
-        assert_equals(pixel[0], 0, "Cross origin should not draw");
-        assert_equals(pixel[1], 128, "Cross origin should not draw");
-        assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
-      });
-    }
+    pixel = context.getImageData(50, 150, 1, 1).data;
+    assert_equals(pixel[0], 0, "Cross origin should not draw");
+    assert_equals(pixel[1], 128, "Cross origin should not draw");
+    assert_equals(pixel[2], 0, "Cross origin should not draw");
+  });
+};
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/background-svg-images-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/background-svg-images-ignored.https.sub.html
index 022d9cf2..0591e40 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/background-svg-images-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/background-svg-images-ignored.https.sub.html
@@ -37,24 +37,24 @@
   </canvas>
 
   <script>
-    window.onload = () => {
-      test(function(t) {
-        var context = canvas.getContext("2d");
-        context.drawElementImage(child, 0, 0);
+window.onload = () => {
+  promise_test(async function(t) {
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
+    var context = canvas.getContext("2d");
+    context.drawElementImage(child, 0, 0);
 
-        pixel = context.getImageData(50, 50, 1, 1).data;
-        assert_equals(pixel[0], 255, "Same origin should draw");
-        assert_equals(pixel[1], 0, "Same origin should draw");
-        assert_equals(pixel[2], 0, "Same origin should draw");
+    pixel = context.getImageData(50, 50, 1, 1).data;
+    assert_equals(pixel[0], 255, "Same origin should draw");
+    assert_equals(pixel[1], 0, "Same origin should draw");
+    assert_equals(pixel[2], 0, "Same origin should draw");
 
-        pixel = context.getImageData(50, 150, 1, 1).data;
-        assert_equals(pixel[0], 0, "Cross origin should not draw");
-        assert_equals(pixel[1], 128, "Cross origin should not draw");
-        assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
-      });
-    }
+    pixel = context.getImageData(50, 150, 1, 1).data;
+    assert_equals(pixel[0], 0, "Cross origin should not draw");
+    assert_equals(pixel[1], 128, "Cross origin should not draw");
+    assert_equals(pixel[2], 0, "Cross origin should not draw");
+  });
+}
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/border-png-images-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/border-png-images-ignored.https.sub.html
index 895e558..617c8a9 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/border-png-images-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/border-png-images-ignored.https.sub.html
@@ -47,7 +47,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
       });
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/border-svg-images-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/border-svg-images-ignored.https.sub.html
index b2865fd8..f4ba83d2 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/border-svg-images-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/border-svg-images-ignored.https.sub.html
@@ -47,7 +47,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
       });
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-mask-png-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-mask-png-ignored.https.sub.html
index 31444de..bc3ce5c 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-mask-png-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-mask-png-ignored.https.sub.html
@@ -40,7 +40,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
 
@@ -64,10 +66,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-mask-svg-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-mask-svg-ignored.https.sub.html
index 6d1f65a..aa1b186 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-mask-svg-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-mask-svg-ignored.https.sub.html
@@ -40,7 +40,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
 
@@ -64,10 +66,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/editing-markers-ignored-ref.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/editing-markers-ignored-ref.html
index e14e7e7..093c188 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/editing-markers-ignored-ref.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/editing-markers-ignored-ref.html
@@ -1,6 +1,8 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage does not draw editing markers - ref</title>
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -17,9 +19,13 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
   onload = () => runTest();
-</script>
\ No newline at end of file
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/editing-markers-ignored.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/editing-markers-ignored.html
index 065e7b3..2685be4a 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/editing-markers-ignored.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/editing-markers-ignored.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage does not draw editing markers</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
@@ -6,6 +7,7 @@
 <link rel="match" href="editing-markers-ignored-ref.html">
 <meta name="assert" value="When preserving privacy for readback, do not draw markers for editing.">
 <script src="/wpt_internal/css/support/markers.js"></script>
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -22,7 +24,7 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
     if (typeof internals !== 'undefined') {
       // To run this test from content_shell you can use
       // "--expose-internals-for-testing" command flag.
@@ -39,8 +41,12 @@
                                     "pink", "thin", "darkred");
     }
 
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
   onload = () => runTest();
 </script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/embed-image-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/embed-image-ignored.https.sub.html
index 4893bc90..6ecf24f 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/embed-image-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/embed-image-ignored.https.sub.html
@@ -33,7 +33,9 @@
   </canvas>
   <script>
     window.onload = function() {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
 
@@ -46,8 +48,6 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     };
   </script>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-multi-filters-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-multi-filters-ignored.https.sub.html
index e564fc38..5181cf6f 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-multi-filters-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-multi-filters-ignored.https.sub.html
@@ -38,7 +38,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d", { willReadFrequently: true });
         context.drawElementImage(document.getElementById("child-same"), 0, 0);
         context.drawElementImage(document.getElementById("child-cross"), 0, 100);
@@ -53,10 +55,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-external-svg-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-external-svg-ignored.https.sub.html
index 325c297..b1c9f92 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-external-svg-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-external-svg-ignored.https.sub.html
@@ -28,7 +28,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d", { willReadFrequently: true });
         context.drawElementImage(document.getElementById("child-same"), 0, 0);
         context.drawElementImage(document.getElementById("child-cross"), 0, 100);
@@ -43,10 +45,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-svg-feimage-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-svg-feimage-ignored.https.sub.html
index 6b9cb32..dfc00dd6 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-svg-feimage-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-svg-feimage-ignored.https.sub.html
@@ -37,7 +37,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d", { willReadFrequently: true });
         context.drawElementImage(document.getElementById("child-same"), 0, 0);
         context.drawElementImage(document.getElementById("child-cross"), 0, 100);
@@ -52,10 +54,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-svg-feimage-svg-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-svg-feimage-svg-ignored.https.sub.html
index 02a6c0ef..709befd 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-svg-feimage-svg-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-svg-feimage-svg-ignored.https.sub.html
@@ -37,7 +37,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d", { willReadFrequently: true });
         context.drawElementImage(document.getElementById("child-same"), 0, 0);
         context.drawElementImage(document.getElementById("child-cross"), 0, 100);
@@ -52,10 +54,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-svg-mixed-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-svg-mixed-ignored.https.sub.html
index 4461c8fb..e3dd9d2 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-svg-mixed-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-div-ref-svg-mixed-ignored.https.sub.html
@@ -40,7 +40,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d", { willReadFrequently: true });
         context.drawElementImage(document.getElementById("child-same"), 0, 0);
         context.drawElementImage(document.getElementById("child-cross"), 0, 100);
@@ -55,10 +57,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-svg-ref-svg-feimage-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-svg-ref-svg-feimage-ignored.https.sub.html
index 7d60ee49..b4b2f6a 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-svg-ref-svg-feimage-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-svg-ref-svg-feimage-ignored.https.sub.html
@@ -23,7 +23,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d", { willReadFrequently: true });
         context.drawElementImage(document.getElementById("child"), 0, 0);
 
@@ -37,7 +39,7 @@
         assert_equals(pixel[1], 128, "Cross origin should not draw, green channel");
         assert_equals(pixel[2], 0, "Cross origin should not draw, blue channel");
 
-        t.done();
+
       });
     }
   </script>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-svg-ref-svg-feimage-svg-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-svg-ref-svg-feimage-svg-ignored.https.sub.html
index ae22496..e4fe27f 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-svg-ref-svg-feimage-svg-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/filtered-svg-ref-svg-feimage-svg-ignored.https.sub.html
@@ -23,7 +23,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d", { willReadFrequently: true });
         context.drawElementImage(document.getElementById("child"), 0, 0);
 
@@ -36,10 +38,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw, red channel");
         assert_equals(pixel[1], 128, "Cross origin should not draw, green channel");
         assert_equals(pixel[2], 0, "Cross origin should not draw, blue channel");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/fragment-mask-png-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/fragment-mask-png-ignored.https.sub.html
index e1a7ec7..d12cfeb 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/fragment-mask-png-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/fragment-mask-png-ignored.https.sub.html
@@ -31,7 +31,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
 
@@ -55,10 +57,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/fragment-mask-svg-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/fragment-mask-svg-ignored.https.sub.html
index 7821020..34dc69d7 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/fragment-mask-svg-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/fragment-mask-svg-ignored.https.sub.html
@@ -31,7 +31,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
 
@@ -55,10 +57,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/glic-markers-ignored-ref.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/glic-markers-ignored-ref.html
index f767d3b3..c34cabc 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/glic-markers-ignored-ref.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/glic-markers-ignored-ref.html
@@ -1,6 +1,8 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage does not draw GLIC markers - ref</title>
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -17,9 +19,13 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
-  onload = () => runTest();
-</script>
\ No newline at end of file
+  onload = runTest;
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/glic-markers-ignored.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/glic-markers-ignored.html
index 220f762..0d687af 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/glic-markers-ignored.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/glic-markers-ignored.html
@@ -24,17 +24,18 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
     if (typeof internals !== 'undefined') {
       // To run this test from content_shell you can use
       // "--expose-internals-for-testing" command flag.
       const range = createRangeForTextOnly(editable, 0, 18);
       internals.setMarker(document, range, "glic");
     }
-    setTimeout(() => {
-      canvas.getContext("2d").drawElementImage(child, 0, 0);
-      takeScreenshot();
-    }, 500);
+    await new Promise(resolve => setTimeout(resolve, 500));
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
+    canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
   onload = () => runTest();
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/grammar-ignored-overlay-path-ref.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/grammar-ignored-overlay-path-ref.html
index e16812d..4c2a455c 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/grammar-ignored-overlay-path-ref.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/grammar-ignored-overlay-path-ref.html
@@ -1,6 +1,8 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage does not draw grammar with custom highlights - ref</title>
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -21,14 +23,18 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
     let r = new Range();
     r.setStart(document.body, 0);
     r.setEnd(document.body, 1);
     CSS.highlights.set("example-highlight", new Highlight(r));
 
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
   onload = () => runTest();
-</script>
\ No newline at end of file
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/grammar-ignored-overlay-path.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/grammar-ignored-overlay-path.html
index b83b912..0433e4a 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/grammar-ignored-overlay-path.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/grammar-ignored-overlay-path.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage does not draw grammar with custom highlights</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
@@ -6,6 +7,7 @@
 <link rel="match" href="grammar-ignored-overlay-path-ref.html">
 <meta name="assert" value="When preserving privacy for readback, do not draw grammar markers when using the highlight overlay painting path (triggered by the custom highlight).">
 <script src="/wpt_internal/css/support/markers.js"></script>
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -30,7 +32,7 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
     addGrammarMarker(editable, 5, 10)
 
     let r = new Range();
@@ -38,8 +40,12 @@
     r.setEnd(document.body, 1);
     CSS.highlights.set("example-highlight", new Highlight(r));
 
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
   onload = () => runTest();
-</script>
\ No newline at end of file
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/html-images-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/html-images-ignored.https.sub.html
index 9b4d61f8..700715ac 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/html-images-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/html-images-ignored.https.sub.html
@@ -31,7 +31,9 @@
   </canvas>
   <script>
     window.onload = function() {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
 
@@ -44,8 +46,6 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     };
   </script>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/iframes-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/iframes-ignored.https.sub.html
index 3c92ff5..e52980b 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/iframes-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/iframes-ignored.https.sub.html
@@ -35,7 +35,9 @@
 
     function pendingIframeLoaded() {
       if (!--sPendingIframesToLoad) {
-        test(function(t) {
+        promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
           var context = canvas.getContext("2d");
           context.drawElementImage(child, 0, 0);
 
@@ -48,8 +50,6 @@
           assert_equals(pixel[0], 0, "Cross origin should not draw");
           assert_equals(pixel[1], 128, "Cross origin should not draw");
           assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-          t.done();
         });
       }
     }
@@ -58,4 +58,4 @@
     crossOrigin.onload = pendingIframeLoaded;
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/object-image-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/object-image-ignored.https.sub.html
index d98c670..6b990d4 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/object-image-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/object-image-ignored.https.sub.html
@@ -33,7 +33,9 @@
   </canvas>
   <script>
     window.onload = function() {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
 
@@ -46,8 +48,6 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     };
   </script>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/picture-images-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/picture-images-ignored.https.sub.html
index 9d4529fa..07b287b7 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/picture-images-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/picture-images-ignored.https.sub.html
@@ -39,7 +39,9 @@
   </canvas>
   <script>
     window.onload = function() {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
 
@@ -52,8 +54,6 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     };
   </script>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/search-text-painted-overlay-path.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/search-text-painted-overlay-path.html
index 4629529d..97c6068 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/search-text-painted-overlay-path.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/search-text-painted-overlay-path.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage draws serach-text with custom highlights</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
@@ -6,6 +7,7 @@
 <link rel="match" href="search-text-painted-overlay-path-ref.html">
 <meta name="assert" value="When preserving privacy for readback, still draw search-text markers when using the highlight overlay painting path (triggered by the custom highlight).">
 <script src="/wpt_internal/css/support/markers.js"></script>
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -30,7 +32,7 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
     addSearchTextMarker(editable, 5, 11)
 
     let r = new Range();
@@ -38,8 +40,13 @@
     r.setEnd(document.body, 1);
     CSS.highlights.set("example-highlight", new Highlight(r));
 
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
+
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
-  onload = () => runTest();
-</script>
\ No newline at end of file
+  onload = runTest;
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/selection-alone-with-decorations-painted.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/selection-alone-with-decorations-painted.html
index 81929865..4765c53 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/selection-alone-with-decorations-painted.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/selection-alone-with-decorations-painted.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage draws selection</title>
 <link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
 <link rel="match" href="selection-alone-with-decorations-painted-ref.html">
 <meta name="assert" value="When preserving privacy for readback, draw selection when it has decorations and shadows.">
 <script src="/wpt_internal/css/support/markers.js"></script>
+<script src="/common/reftest-wait.js"></script>
 <style>
   :focus {
     outline: none;
@@ -30,11 +32,16 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
     setSelection(editable, 5, 11)
 
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
+
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
-  onload = () => runTest();
-</script>
\ No newline at end of file
+  onload = runTest;
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/selection-painted-overlay-path.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/selection-painted-overlay-path.html
index 6d8904a4..2acd7b26 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/selection-painted-overlay-path.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/selection-painted-overlay-path.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage draws selection with custom highlights</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
@@ -6,6 +7,7 @@
 <link rel="match" href="selection-painted-overlay-path-ref.html">
 <meta name="assert" value="When preserving privacy for readback, do draw selection when using the highlight overlay painting path (triggered by the custom highlight).">
 <script src="/wpt_internal/css/support/markers.js"></script>
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -35,7 +37,7 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
     setSelection(editable, 5, 11)
 
     let r = new Range();
@@ -43,8 +45,13 @@
     r.setEnd(editable, 1);
     CSS.highlights.set("example-highlight", new Highlight(r));
 
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
+
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
-  onload = () => runTest();
-</script>
\ No newline at end of file
+  onload = runTest;
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/shape-image-png-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/shape-image-png-ignored.https.sub.html
index 5913288..4ce5385 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/shape-image-png-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/shape-image-png-ignored.https.sub.html
@@ -50,7 +50,9 @@
   </canvas>
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d", { willReadFrequently: true });
         context.drawElementImage(document.getElementById("child"), 0, 0);
 
@@ -100,10 +102,8 @@
         assert_equals(pixel[0], 255, "Cross origin should not draw, red channel");
         assert_equals(pixel[1], 0, "Cross origin should not draw, green channel");
         assert_equals(pixel[2], 0, "Cross origin should not draw, blue channel");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/shape-image-svg-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/shape-image-svg-ignored.https.sub.html
index 502e926f..a1add57a 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/shape-image-svg-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/shape-image-svg-ignored.https.sub.html
@@ -52,7 +52,9 @@
   </canvas>
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d", { willReadFrequently: true });
         context.drawElementImage(document.getElementById("child"), 0, 0);
 
@@ -102,10 +104,8 @@
         assert_equals(pixel[0], 255, "Cross origin should not draw, red channel");
         assert_equals(pixel[1], 0, "Cross origin should not draw, green channel");
         assert_equals(pixel[2], 0, "Cross origin should not draw, blue channel");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-fast-path-ref.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-fast-path-ref.html
index 89920f8..2d3f334 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-fast-path-ref.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-fast-path-ref.html
@@ -1,6 +1,8 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage does not draw spelling - ref</title>
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -17,9 +19,13 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
   onload = () => runTest();
-</script>
\ No newline at end of file
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-fast-path.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-fast-path.html
index ea7d6af..1b9f2b3 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-fast-path.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-fast-path.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage does not draw spelling</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
@@ -6,6 +7,7 @@
 <link rel="match" href="spelling-ignored-fast-path-ref.html">
 <meta name="assert" value="When preserving privacy for readback, do not draw spelling markers when using the fast path.">
 <script src="/wpt_internal/css/support/markers.js"></script>
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -22,11 +24,15 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
     addSpellingMarker(editable, 5, 10)
 
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
   onload = () => runTest();
-</script>
\ No newline at end of file
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-overlay-path-ref.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-overlay-path-ref.html
index b51bfcb7..a289621 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-overlay-path-ref.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-overlay-path-ref.html
@@ -1,6 +1,8 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage does not draw spelling with custom highlights - ref</title>
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -21,14 +23,18 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
     let r = new Range();
     r.setStart(document.body, 0);
     r.setEnd(document.body, 1);
     CSS.highlights.set("example-highlight", new Highlight(r));
 
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
   onload = () => runTest();
-</script>
\ No newline at end of file
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-overlay-path.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-overlay-path.html
index 57fa4397..dd2e79e 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-overlay-path.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/spelling-ignored-overlay-path.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage does not draw spelling with custom highlights</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
@@ -6,6 +7,7 @@
 <link rel="match" href="spelling-ignored-overlay-path-ref.html">
 <meta name="assert" value="When preserving privacy for readback, do not draw spelling markers when using the highlight overlay painting path (triggered by the custom highlight).">
 <script src="/wpt_internal/css/support/markers.js"></script>
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -30,7 +32,7 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
     addSpellingMarker(editable, 5, 10)
 
     let r = new Range();
@@ -38,8 +40,12 @@
     r.setEnd(document.body, 1);
     CSS.highlights.set("example-highlight", new Highlight(r));
 
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
   onload = () => runTest();
-</script>
\ No newline at end of file
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/subframe-cross-origin-frame.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/subframe-cross-origin-frame.https.sub.html
index f83e3e8f..156d5174 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/subframe-cross-origin-frame.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/subframe-cross-origin-frame.https.sub.html
@@ -9,10 +9,10 @@
   <script src='/resources/testharnessreport.js'></script>
 </head>
 <body>
-  <iframe id=crossOrigin width=100 height=100 src="https://{{hosts[alt][www]}}:{{ports[h2][0]}}/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-iframe.https.sub.html"></iframe>
+  <iframe id=crossOrigin width=100 height=200 src="https://{{hosts[alt][www]}}:{{ports[h2][0]}}/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-iframe.https.sub.html"></iframe>
 
   <script>
     fetch_tests_from_window(crossOrigin.contentWindow);
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-background-png-images.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-background-png-images.https.sub.html
index 3f71be8..6121e7be 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-background-png-images.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-background-png-images.https.sub.html
@@ -38,7 +38,9 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d", { willReadFrequently: true });
         context.drawElementImage(child, 0, 0);
 
@@ -51,10 +53,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-html-images.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-html-images.https.sub.html
index 3251815..07d19af2 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-html-images.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-html-images.https.sub.html
@@ -31,7 +31,9 @@
   </canvas>
   <script>
     window.onload = function() {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
 
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-iframe.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-iframe.https.sub.html
index 8511d09..ed8320e 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-iframe.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/support/subframe-with-canvas-iframe.https.sub.html
@@ -39,7 +39,9 @@
 
     function pendingIframeLoaded() {
       if (!--sPendingIframesToLoad) {
-        test(function(t) {
+        promise_test(async function(t) {
+          await new Promise(requestAnimationFrame);
+          await new Promise(setTimeout);
           var context = canvas.getContext("2d", { willReadFrequently: true });
           context.drawElementImage(child, 0, 0);
 
@@ -52,8 +54,6 @@
           assert_equals(pixel[0], 255, "Same origin should draw");
           assert_equals(pixel[1], 0, "Same origin should draw");
           assert_equals(pixel[2], 0, "Same origin should draw");
-
-          t.done();
         });
       }
     }
@@ -62,4 +62,4 @@
     crossOrigin.onload = pendingIframeLoaded;
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-images-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-images-ignored.https.sub.html
index feba6cf8..0ad3efe 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-images-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-images-ignored.https.sub.html
@@ -16,7 +16,9 @@
 
   <script>
     window.onload = function() {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
 
@@ -29,8 +31,6 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     };
   </script>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-use-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-use-ignored.https.sub.html
index 6e845619..fcd3986 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-use-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-use-ignored.https.sub.html
@@ -19,10 +19,16 @@
 
   <script>
     window.onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d", { willReadFrequently: true });
         context.drawElementImage(document.getElementById("child-same"), 0, 0);
-        context.drawElementImage(document.getElementById("child-cross"), 0, 100);
+        assert_throws_dom(
+          "InvalidStateError",
+          () => context.drawElementImage(document.getElementById("child-cross"), 0, 100),
+          'drawElementImage cannot draw cross-origin svg `use`.'
+        );
 
         pixel = context.getImageData(50, 50, 1, 1).data;
         assert_equals(pixel[0], 0, "Same origin should draw red");
@@ -34,10 +40,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw red");
         assert_equals(pixel[1], 0, "Cross origin should not draw green");
         assert_equals(pixel[2], 0, "Cross origin should not draw blue");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/target-text-ignored-overlay-path-ref.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/target-text-ignored-overlay-path-ref.html
index 4e493dfe..5215401 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/target-text-ignored-overlay-path-ref.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/target-text-ignored-overlay-path-ref.html
@@ -1,6 +1,8 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage does not draw target-text with custom highlights - ref</title>
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -21,14 +23,18 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
     let r = new Range();
     r.setStart(document.body, 0);
     r.setEnd(document.body, 1);
     CSS.highlights.set("example-highlight", new Highlight(r));
 
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
   onload = () => runTest();
-</script>
\ No newline at end of file
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/target-text-ignored-overlay-path.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/target-text-ignored-overlay-path.html
index 4ea9039..9a875a4 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/target-text-ignored-overlay-path.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/target-text-ignored-overlay-path.html
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>drawElementImage does not draw target-text with custom highlights</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="target-text-ignored-overlay-path-ref.html">
 <meta name="assert" value="When preserving privacy for readback, do not draw target text markers when using the highlight overlay painting path (triggered by the custom highlight).">
+<script src="/common/reftest-wait.js"></script>
 <style>
   #child {
     width: 100px;
@@ -29,7 +31,7 @@
   </div>
 </canvas>
 <script>
-  function runTest() {
+  async function runTest() {
     window.location.hash = "#:~:text=Match";
 
     let r = new Range();
@@ -37,8 +39,12 @@
     r.setEnd(document.body, 1);
     CSS.highlights.set("example-highlight", new Highlight(r));
 
+    await new Promise(requestAnimationFrame);
+    await new Promise(setTimeout);
     canvas.getContext("2d").drawElementImage(child, 0, 0);
+    takeScreenshot();
   }
 
   onload = () => runTest();
-</script>
\ No newline at end of file
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/video-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/video-ignored.https.sub.html
index 7de33a7..18950da 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/video-ignored.https.sub.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/video-ignored.https.sub.html
@@ -40,7 +40,9 @@
 
   <script>
     onload = () => {
-      test(function(t) {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
         var context = canvas.getContext("2d");
         context.drawElementImage(child, 0, 0);
 
@@ -53,10 +55,8 @@
         assert_equals(pixel[0], 0, "Cross origin should not draw");
         assert_equals(pixel[1], 128, "Cross origin should not draw");
         assert_equals(pixel[2], 0, "Cross origin should not draw");
-
-        t.done();
       });
     }
   </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/scale.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/scale.html
index 7d9969e..6ccaa2f 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/scale.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/scale.html
@@ -59,6 +59,7 @@
 
 Promise.all(Array.from(document.querySelectorAll("canvas")).map(async cvs => {
   await resizeToPixelGrid(cvs);
+  await new Promise(setTimeout);
   const target = cvs.firstElementChild;
   const ctx = cvs.getContext("2d");
   if (target.hasAttribute("xscale") && target.hasAttribute("yscale")) {
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/shadow-basic.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/shadow-basic.html
index c01078d..9914bab 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/shadow-basic.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/shadow-basic.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: green rect with shadow</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="shadow-basic-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <meta name=fuzzy content="0-2;0-1990">
 <style>
 #child {
@@ -28,9 +29,10 @@
   context.shadowOffsetX = 20;
   context.shadowOffsetY = 10;
   context.drawElementImage(child, 20, 30);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/shadow-non-opaque-element.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/shadow-non-opaque-element.html
index f019f466..48d80172 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/shadow-non-opaque-element.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/shadow-non-opaque-element.html
@@ -1,10 +1,11 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: non opaque element with compositing op</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="shadow-non-opaque-element-ref.html">
 <meta name=fuzzy content="0-2;0-2185">
+<script src="/common/reftest-wait.js"></script>
 <style>
 #child {
   width: 100px;
@@ -39,9 +40,10 @@
   context.shadowOffsetX = 20;
   context.shadowOffsetY = 10;
   context.drawElementImage(child, 20, 30);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/svg-foreign-object.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/svg-foreign-object.html
index 8118271..f9d9e629 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/svg-foreign-object.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/svg-foreign-object.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: SVG foreignObject green rect</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="basic-rect-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
 #canvas {
   background: grey;
@@ -21,9 +22,10 @@
 <script>
 function runTest() {
   canvas.getContext("2d").drawElementImage(child, 0, 0);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/svg-rect.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/svg-rect.html
index 894be01..78eba35 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/svg-rect.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/svg-rect.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<html>
+<html class="reftest-wait">
 <title>Canvas.drawElementImage: SVG green rect</title>
 <link rel="help" href="https://github.com/WICG/html-in-canvas">
 <link rel="author" href="mailto:schenney@chromium.org">
 <link rel="match" href="basic-rect-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
 #canvas {
   background: grey;
@@ -19,9 +20,10 @@
 <script>
 function runTest() {
   canvas.getContext("2d").drawElementImage(child, 20, 30);
+  takeScreenshot();
 }
 
-onload = () => runTest();
+onload = () => requestAnimationFrame(() => setTimeout(runTest));
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/tex-element-image-2d-basic.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/tex-element-image-2d-basic.html
index 43888a3d..dee85e6a 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/tex-element-image-2d-basic.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/tex-element-image-2d-basic.html
@@ -188,7 +188,7 @@
         }, "verify the texElementImage2D works as expected");
       }
 
-      onload = () => runTest();
+      onload = () => requestAnimationFrame(() => setTimeout(runTest));
     </script>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/top-layer-not-painted-in-canvas.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/top-layer-not-painted-in-canvas.html
index 75dbe7a..d538b2b7 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/top-layer-not-painted-in-canvas.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/top-layer-not-painted-in-canvas.html
@@ -27,12 +27,15 @@
 </canvas>
 
 <script>
-onload = () => {
+async function runTest() {
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
   dialog.showModal();
-  requestAnimationFrame(() => {
-    canvas.getContext('2d').drawElementImage(target, 100, 0);
-    requestAnimationFrame(takeScreenshot);
-  });
+  await new Promise(requestAnimationFrame);
+  await new Promise(setTimeout);
+  canvas.getContext('2d').drawElementImage(target, 100, 0);
+  takeScreenshot();
 }
+onload = () => runTest();
 </script>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/webgl-css-transform-on-div.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/webgl-css-transform-on-div.html
index 06e9fa5..a4c29e4 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/webgl-css-transform-on-div.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/webgl-css-transform-on-div.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <head>
   <title>texElementImage2D ignores CSS transforms</title>
   <link rel="help" href="https://github.com/WICG/html-in-canvas">
   <link rel="match" href="css-transform-on-div-ref.html">
   <meta name="assert" value="A div with a CSS transform paints without the transform.">
+  <script src="/common/reftest-wait.js"></script>
   <style>
     canvas {
       background: black;
@@ -29,9 +31,11 @@
 
 const canvas = document.querySelector("canvas");
 await resizeToPixelGrid(canvas);
+await new Promise(setTimeout);
 const prog = new SimpleGLProgram(canvas.getContext("webgl2"));
 const target = canvas.firstElementChild;
 prog.render(target);
 takeScreenshot();
   </script>
 </body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/webgl-ink-overflow.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/webgl-ink-overflow.html
index 7349e24..0384f6c0 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/webgl-ink-overflow.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/webgl-ink-overflow.html
@@ -62,6 +62,7 @@
 
 Promise.all(Array.from(document.querySelectorAll("canvas")).map(async canvas => {
   await resizeToPixelGrid(canvas);
+  await new Promise(setTimeout);
   const prog = new SimpleGLProgram(canvas.getContext("webgl2"));
   const target = canvas.firstElementChild;
   const targetStyle = getComputedStyle(target);
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/webgl-scaling.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/webgl-scaling.html
index 0db1097c..f52a015 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/webgl-scaling.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/webgl-scaling.html
@@ -60,6 +60,7 @@
 
 Promise.all(Array.from(document.querySelectorAll("canvas")).map(async canvas => {
   await resizeToPixelGrid(canvas);
+  await new Promise(setTimeout);
   const prog = new SimpleGLProgram(canvas.getContext("webgl2"));
   const target = canvas.firstElementChild;
   if (target.hasAttribute("xscale") && target.hasAttribute("yscale")) {
diff --git a/third_party/catapult b/third_party/catapult
index 87c9af3..0f9739d 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit 87c9af3b7c6495ab0a0ba86a3eb6a58ff84eca85
+Subproject commit 0f9739da7f76bbb5bc9c108986880d5cfb9b9cf6
diff --git a/third_party/dawn b/third_party/dawn
index a1b78e4d..0e9fde4 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit a1b78e4d9d2b8ac509af227aff8e3004d2ace7f1
+Subproject commit 0e9fde4e362e8e402c7ca3cc3d241775eb80bdb8
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index c804bd5..e27f62c 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit c804bd56a0e5089b60ad36855f181564fa114789
+Subproject commit e27f62c91b96b3f59a6322ee587f80bfa2d9ed96
diff --git a/third_party/eigen3/src b/third_party/eigen3/src
index f64d1e0a..25dba49 160000
--- a/third_party/eigen3/src
+++ b/third_party/eigen3/src
@@ -1 +1 @@
-Subproject commit f64d1e0acc2c2c33e325b8dd7b2b4673de2b9f14
+Subproject commit 25dba492e30e19dce3d4bd4cd38af2f5c88c387c
diff --git a/third_party/fuzztest/src b/third_party/fuzztest/src
index 362a279..f67a6fe 160000
--- a/third_party/fuzztest/src
+++ b/third_party/fuzztest/src
@@ -1 +1 @@
-Subproject commit 362a279f0ad018574278e72c4e98e2b99d3991bb
+Subproject commit f67a6fe9ca3afc4d71997e75e8d87c571f0c30cb
diff --git a/third_party/openscreen/src b/third_party/openscreen/src
index d55bd31..89fa62b 160000
--- a/third_party/openscreen/src
+++ b/third_party/openscreen/src
@@ -1 +1 @@
-Subproject commit d55bd311eb9a970a8e4fb25cbe5ae016d4d3d812
+Subproject commit 89fa62bbbe45c5cc87c6a5e84ff3f2d03a3656c8
diff --git a/third_party/pdfium b/third_party/pdfium
index 65f4269..fcfdf1c 160000
--- a/third_party/pdfium
+++ b/third_party/pdfium
@@ -1 +1 @@
-Subproject commit 65f4269f6accf48306adb7fff10c07d72d56b1ea
+Subproject commit fcfdf1c3e22ed9d0f2341d596297393cc7f5ad53
diff --git a/third_party/perfetto b/third_party/perfetto
index dcb5e98..fa49f16 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit dcb5e98f9f6e9ba61a95b0a3fef56671b0b6ba83
+Subproject commit fa49f16ad958de750da23698f12aebb9f1ae662f
diff --git a/third_party/skia b/third_party/skia
index 9333e22..28172a4 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 9333e227108653e751321a2420f88ae6dc2156ae
+Subproject commit 28172a4e03af8430d1bb6497116baa4b260ad4eb
diff --git a/third_party/spirv-tools/src b/third_party/spirv-tools/src
index 1c8c845..4972c69 160000
--- a/third_party/spirv-tools/src
+++ b/third_party/spirv-tools/src
@@ -1 +1 @@
-Subproject commit 1c8c845843ca77f70a862fc94d371d36fe703498
+Subproject commit 4972c69eb50255b314fc0925ca757c4417e6b6c0
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps
index 92727ef..dd5ab01 160000
--- a/third_party/vulkan-deps
+++ b/third_party/vulkan-deps
@@ -1 +1 @@
-Subproject commit 92727ef4b277d71d38659e8679bd9f3c59651401
+Subproject commit dd5ab0120a15c8373e8cfa6278b23918dbe09dbf
diff --git a/third_party/vulkan-validation-layers/src b/third_party/vulkan-validation-layers/src
index cbfe559..9076c34 160000
--- a/third_party/vulkan-validation-layers/src
+++ b/third_party/vulkan-validation-layers/src
@@ -1 +1 @@
-Subproject commit cbfe559d89689bd5e471a39560e137043fb0dc36
+Subproject commit 9076c341487013d4650377e164fd4816779f501d
diff --git a/third_party/webrtc b/third_party/webrtc
index 1b08071..6f90f4f 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit 1b08071e7a4747ae7614aa7898d0d393bbe297a0
+Subproject commit 6f90f4ff7a7fb27e8e6785bf40c5ca46b6254162
diff --git a/tools/autotest/main.py b/tools/autotest/main.py
index d16d747..c33c3fa 100644
--- a/tools/autotest/main.py
+++ b/tools/autotest/main.py
@@ -54,14 +54,31 @@
                help=__doc__,
                context_settings=dict(ignore_unknown_options=True,
                                      allow_interspersed_args=True,
+                                     allow_extra_args=True,
                                      help_option_names=['-h', '--help']))
 @autotest_options
-@click.argument('files', nargs=-1)
 @click.pass_context
 @telemetry.tracer.start_as_current_span('chromium.tools.autotest.main')
 def main(ctx, **kwargs) -> int:
-  # Capture "extras" (arguments click didn't recognize)
-  kwargs['extras'] = ctx.args
+
+  files_to_test = []
+  extras = []
+
+  parsing_files = True
+  for arg in ctx.args:
+    if len(files_to_test) == 0:
+      parsing_files = True
+
+    if arg.startswith('-'):
+      parsing_files = False
+
+    if parsing_files:
+      files_to_test.append(arg)
+    else:
+      extras.append(arg)
+
+  kwargs['files'] = tuple(files_to_test)
+  kwargs['extras'] = extras
 
   config: AutotestConfig = AutotestConfig(**kwargs)
 
diff --git a/extensions/common/api/content_scripts.idl b/tools/json_schema_compiler/test/converted_schemas/content_scripts.idl
similarity index 100%
rename from extensions/common/api/content_scripts.idl
rename to tools/json_schema_compiler/test/converted_schemas/content_scripts.idl
diff --git a/tools/json_schema_compiler/test/converted_schemas/content_scripts.webidl b/tools/json_schema_compiler/test/converted_schemas/content_scripts.webidl
new file mode 100644
index 0000000..b325e022
--- /dev/null
+++ b/tools/json_schema_compiler/test/converted_schemas/content_scripts.webidl
@@ -0,0 +1,80 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[ExternalExtensionType="extensionTypes.RunAt"]
+typedef object ExtensionTypesRunAt;
+
+[ExternalExtensionType="extensionTypes.ExecutionWorld"]
+typedef object ExtensionTypesExecutionWorld;
+
+// Describes a content script to be injected into a web page.
+dictionary ContentScript {
+  // Specifies which pages this content script will be injected into. See
+  // <a href="develop/concepts/match-patterns">Match Patterns</a> for more
+  // details on the syntax of these strings.
+  required sequence<DOMString> matches;
+
+  // Excludes pages that this content script would otherwise be injected into.
+  // See <a href="develop/concepts/match-patterns">Match Patterns</a> for more
+  // details on the syntax of these strings.
+  sequence<DOMString> exclude_matches;
+
+  // The list of CSS files to be injected into matching pages. These are
+  // injected in the order they appear in this array, before any DOM is
+  // constructed or displayed for the page.
+  sequence<DOMString> css;
+
+  // The list of JavaScript files to be injected into matching pages. These
+  // are injected in the order they appear in this array.
+  sequence<DOMString> js;
+
+  // If specified true, it will inject into all frames, even if the frame is
+  // not the top-most frame in the tab. Each frame is checked independently
+  // for URL requirements; it will not inject into child frames if the URL
+  // requirements are not met. Defaults to false, meaning that only the top
+  // frame is matched.
+  boolean all_frames;
+
+  // Whether the script should inject into any frames where the URL belongs to
+  // a scheme that would never match a specified Match Pattern, including
+  // about:, data:, blob:, and filesystem: schemes. In these cases, in order
+  // to determine if the script should inject, the origin of the URL is
+  // checked. If the origin is `null` (as is the case for data: URLs), then
+  // the "initiator" or "creator" origin is used (i.e., the origin of the
+  // frame that created or navigated this frame). Note that this may not
+  // be the parent frame, if the frame was navigated by another frame in the
+  // document hierarchy.
+  boolean match_origin_as_fallback;
+
+  // Whether the script should inject into an about:blank frame where the
+  // parent or opener frame matches one of the patterns declared in matches.
+  // Defaults to false.
+  boolean match_about_blank;
+
+  // Applied after matches to include only those URLs that also match this
+  // glob. Intended to emulate the
+  // <a href="http://wiki.greasespot.net/Metadata_Block#.40include">@include
+  // </a> Greasemonkey keyword.
+  sequence<DOMString> include_globs;
+
+  // Applied after matches to exclude URLs that match this glob. Intended to
+  // emulate the
+  // <a href="https://wiki.greasespot.net/Metadata_Block#.40exclude">@exclude
+  // </a> Greasemonkey keyword.
+  sequence<DOMString> exclude_globs;
+
+  // Specifies when JavaScript files are injected into the web page. The
+  // preferred and default value is <code>document_idle</code>.
+  ExtensionTypesRunAt run_at;
+
+  // The JavaScript "world" to run the script in. Defaults to
+  // <code>ISOLATED</code>. Only available in Manifest V3 extensions.
+  ExtensionTypesExecutionWorld world;
+};
+
+// Stub namespace for the "content_scripts" manifest key.
+[generate_error_messages, Namespace=contentScripts]
+partial dictionary ExtensionManifest {
+  sequence<ContentScript> content_scripts;
+};
diff --git a/tools/json_schema_compiler/web_idl_diff_tool_test.py b/tools/json_schema_compiler/web_idl_diff_tool_test.py
index 2e48eee..ad6dd95 100755
--- a/tools/json_schema_compiler/web_idl_diff_tool_test.py
+++ b/tools/json_schema_compiler/web_idl_diff_tool_test.py
@@ -60,6 +60,7 @@
         ('power.idl', 'power.webidl'),
         ('serial.idl', 'serial.webidl'),
         ('bluetooth_private.idl', 'bluetooth_private.webidl'),
+        ('content_scripts.idl', 'content_scripts.webidl'),
     ]
     # LoadAndReturnUnifiedDiff expects file paths relative to the repo root.
     converted_schema_path = 'tools/json_schema_compiler/test/converted_schemas/'
diff --git a/tools/metrics/histograms/metadata/accessibility/histograms.xml b/tools/metrics/histograms/metadata/accessibility/histograms.xml
index a9e09f22..ce21f4a 100644
--- a/tools/metrics/histograms/metadata/accessibility/histograms.xml
+++ b/tools/metrics/histograms/metadata/accessibility/histograms.xml
@@ -3402,6 +3402,18 @@
   </summary>
 </histogram>
 
+<histogram
+    name="Accessibility.ReadAnything.TimeFromStartDistillationToOnArticleReady"
+    units="ms" expires_after="2027-03-01">
+  <owner>martinglopez@google.com</owner>
+  <owner>chrome-irm-devs@google.com</owner>
+  <summary>
+    Records the time it takes for the Readability distiller to complete a
+    distillation process. This starts when StartDistillation is called and ends
+    when OnArticleReady is called.
+  </summary>
+</histogram>
+
 <histogram name="Accessibility.ReadAnything.TimeFrom{Flow}StartedTo{Method}"
     units="ms" expires_after="2026-08-02">
   <owner>lwinston@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/sb_client/enums.xml b/tools/metrics/histograms/metadata/sb_client/enums.xml
index aac92b12..10c357e 100644
--- a/tools/metrics/histograms/metadata/sb_client/enums.xml
+++ b/tools/metrics/histograms/metadata/sb_client/enums.xml
@@ -457,10 +457,6 @@
       label="Phishing classifier callback is empty on classification
              completion"/>
   <int value="9" label="Phishing classification request is responded"/>
-  <int value="10" label="Renderer page layout has finished loading"/>
-  <int value="11"
-      label="Renderer page layout has finished loading again in middle of
-             classification"/>
 </enum>
 
 <enum name="TailoredWarningType">
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index e0bc4371..d18b90a 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,7 +6,7 @@
         },
         "win": {
             "hash": "e31aeb7477b718b8a4817e024588f8996bb627b1",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/ca4508e8f261cdaf3d5b48c9381a504b99b2669a/trace_processor_shell.exe"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/fa49f16ad958de750da23698f12aebb9f1ae662f/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "46d798c1864490cbb2ee053d6eda436184470e69",
@@ -22,7 +22,7 @@
         },
         "linux": {
             "hash": "d5d0a855a03ac3281a75d252c868dbcb97d9ada7",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/dcb5e98f9f6e9ba61a95b0a3fef56671b0b6ba83/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/fa49f16ad958de750da23698f12aebb9f1ae662f/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/typescript/definitions/pending.d.ts b/tools/typescript/definitions/pending.d.ts
index cb3b3f7b..5752a05 100644
--- a/tools/typescript/definitions/pending.d.ts
+++ b/tools/typescript/definitions/pending.d.ts
@@ -155,3 +155,9 @@
         locales: string|string[], options?: DurationFormatOptions): string[],
   };
 }
+
+// See https://github.com/microsoft/TypeScript/issues/46135.
+declare module '*.css' {
+  const _default: CSSStyleSheet;
+  export default _default;
+}
diff --git a/ui/views/win/fullscreen_handler.cc b/ui/views/win/fullscreen_handler.cc
index b51df0e8..4a26b26 100644
--- a/ui/views/win/fullscreen_handler.cc
+++ b/ui/views/win/fullscreen_handler.cc
@@ -65,18 +65,18 @@
 
   // Save current window state if not already fullscreen.
   if (!fullscreen_) {
-    saved_window_info_.style = GetWindowLong(hwnd_, GWL_STYLE);
-    saved_window_info_.ex_style = GetWindowLong(hwnd_, GWL_EXSTYLE);
+    saved_window_info_.style = ::GetWindowLong(hwnd_, GWL_STYLE);
+    saved_window_info_.ex_style = ::GetWindowLong(hwnd_, GWL_EXSTYLE);
     // Store the original window rect, DPI, and monitor info to detect changes
     // and more accurately restore window placements when exiting fullscreen.
     ::GetWindowRect(hwnd_, &saved_window_info_.rect);
     saved_window_info_.dpi = display::win::GetScreenWin()->GetDPIForHWND(hwnd_);
     saved_window_info_.monitor =
-        MonitorFromWindow(hwnd_, MONITOR_DEFAULTTONEAREST);
+        ::MonitorFromWindow(hwnd_, MONITOR_DEFAULTTONEAREST);
     saved_window_info_.monitor_info.cbSize =
         sizeof(saved_window_info_.monitor_info);
-    GetMonitorInfo(saved_window_info_.monitor,
-                   &saved_window_info_.monitor_info);
+    ::GetMonitorInfo(saved_window_info_.monitor,
+                     &saved_window_info_.monitor_info);
   }
 
   fullscreen_ = fullscreen;
@@ -84,9 +84,9 @@
   auto ref = weak_ptr_factory_.GetWeakPtr();
   if (fullscreen_) {
     // Set new window style and size.
-    SetWindowLong(hwnd_, GWL_STYLE,
-                  saved_window_info_.style & ~(WS_CAPTION | WS_THICKFRAME));
-    SetWindowLong(
+    ::SetWindowLong(hwnd_, GWL_STYLE,
+                    saved_window_info_.style & ~(WS_CAPTION | WS_THICKFRAME));
+    ::SetWindowLong(
         hwnd_, GWL_EXSTYLE,
         saved_window_info_.ex_style & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
                                         WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
@@ -98,29 +98,29 @@
     gfx::Rect window_rect = screen_win_display.screen_rect();
     if (target_display_id == display::kInvalidDisplayId ||
         screen_win_display.display().id() != target_display_id) {
-      HMONITOR monitor = MonitorFromWindow(hwnd_, MONITOR_DEFAULTTONEAREST);
+      HMONITOR monitor = ::MonitorFromWindow(hwnd_, MONITOR_DEFAULTTONEAREST);
       MONITORINFO monitor_info;
       monitor_info.cbSize = sizeof(monitor_info);
-      GetMonitorInfo(monitor, &monitor_info);
+      ::GetMonitorInfo(monitor, &monitor_info);
       window_rect = gfx::Rect(monitor_info.rcMonitor);
     }
-    SetWindowPos(hwnd_, nullptr, window_rect.x(), window_rect.y(),
-                 window_rect.width(), window_rect.height(),
-                 SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+    ::SetWindowPos(hwnd_, nullptr, window_rect.x(), window_rect.y(),
+                   window_rect.width(), window_rect.height(),
+                   SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
   } else {
     // Restore the window style and bounds saved prior to entering fullscreen.
     // Use WS_VISIBLE for windows shown after SetFullscreen: crbug.com/1062251.
     // Making multiple window adjustments here is ugly, but if SetWindowPos()
     // doesn't redraw, the taskbar won't be repainted.
-    SetWindowLong(hwnd_, GWL_STYLE, saved_window_info_.style | WS_VISIBLE);
-    SetWindowLong(hwnd_, GWL_EXSTYLE, saved_window_info_.ex_style);
+    ::SetWindowLong(hwnd_, GWL_STYLE, saved_window_info_.style | WS_VISIBLE);
+    ::SetWindowLong(hwnd_, GWL_EXSTYLE, saved_window_info_.ex_style);
 
     gfx::Rect window_rect(saved_window_info_.rect);
     HMONITOR monitor =
-        MonitorFromRect(&saved_window_info_.rect, MONITOR_DEFAULTTONEAREST);
+        ::MonitorFromRect(&saved_window_info_.rect, MONITOR_DEFAULTTONEAREST);
     MONITORINFO monitor_info;
     monitor_info.cbSize = sizeof(monitor_info);
-    GetMonitorInfo(monitor, &monitor_info);
+    ::GetMonitorInfo(monitor, &monitor_info);
     // Adjust the window bounds to restore, if displays were disconnected,
     // virtually rearranged, or otherwise changed metrics during fullscreen.
     if (monitor != saved_window_info_.monitor ||
@@ -131,9 +131,9 @@
     const int fullscreen_dpi =
         display::win::GetScreenWin()->GetDPIForHWND(hwnd_);
 
-    SetWindowPos(hwnd_, nullptr, window_rect.x(), window_rect.y(),
-                 window_rect.width(), window_rect.height(),
-                 SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+    ::SetWindowPos(hwnd_, nullptr, window_rect.x(), window_rect.y(),
+                   window_rect.width(), window_rect.height(),
+                   SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
 
     const int final_dpi = display::win::GetScreenWin()->GetDPIForHWND(hwnd_);
     if (final_dpi != saved_window_info_.dpi || final_dpi != fullscreen_dpi) {
@@ -150,9 +150,9 @@
         window_rect.set_size(gfx::ToCeiledSize(size));
         window_rect.AdjustToFit(gfx::Rect(monitor_info.rcWork));
       }
-      SetWindowPos(hwnd_, nullptr, window_rect.x(), window_rect.y(),
-                   window_rect.width(), window_rect.height(),
-                   SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+      ::SetWindowPos(hwnd_, nullptr, window_rect.x(), window_rect.y(),
+                     window_rect.width(), window_rect.height(),
+                     SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
     }
   }
   if (!ref) {
diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc
index 9735afc..6a4f780 100644
--- a/ui/views/win/hwnd_message_handler.cc
+++ b/ui/views/win/hwnd_message_handler.cc
@@ -204,9 +204,9 @@
                                                LPARAM l_param) {
   if (n_code == HC_ACTION && w_param == VK_ESCAPE) {
     int value = TRUE;
-    DwmSetWindowAttribute(instance_->host_->hwnd(),
-                          DWMWA_TRANSITIONS_FORCEDISABLED, &value,
-                          sizeof(value));
+    ::DwmSetWindowAttribute(instance_->host_->hwnd(),
+                            DWMWA_TRANSITIONS_FORCEDISABLED, &value,
+                            sizeof(value));
     if (instance_->hide_on_escape_) {
       instance_->host_->Hide();
     }
@@ -217,7 +217,7 @@
 // Called from OnNCActivate.
 BOOL CALLBACK EnumChildWindowsForRedraw(HWND hwnd, LPARAM lparam) {
   DWORD process_id;
-  GetWindowThreadProcessId(hwnd, &process_id);
+  ::GetWindowThreadProcessId(hwnd, &process_id);
   UINT flags = RDW_INVALIDATE | RDW_NOCHILDREN | RDW_FRAME;
   if (process_id == GetCurrentProcessId()) {
     flags |= RDW_UPDATENOW;
@@ -356,7 +356,7 @@
         hwnd_(owner_->hwnd()),
         should_lock_(owner_->IsVisible() && !owner->HasChildRenderingWindow() &&
                      ::IsWindow(hwnd_) && !owner_->IsHeadless() &&
-                     (!(GetWindowLong(hwnd_, GWL_STYLE) & WS_CAPTION))) {
+                     (!(::GetWindowLong(hwnd_, GWL_STYLE) & WS_CAPTION))) {
     if (should_lock_) {
       owner_->LockUpdates();
     }
@@ -474,7 +474,7 @@
 }
 
 void HWNDMessageHandler::Close() {
-  if (!IsWindow(hwnd())) {
+  if (!::IsWindow(hwnd())) {
     return;  // No need to do anything.
   }
 
@@ -506,22 +506,22 @@
   // we need to check to see if we're still a window before trying to destroy
   // ourself.
   waiting_for_close_now_ = false;
-  if (IsWindow(hwnd())) {
-    DestroyWindow(hwnd());
+  if (::IsWindow(hwnd())) {
+    ::DestroyWindow(hwnd());
   }
 }
 
 gfx::Rect HWNDMessageHandler::GetWindowBoundsInScreen() const {
   RECT r;
-  GetWindowRect(hwnd(), &r);
+  ::GetWindowRect(hwnd(), &r);
   return gfx::Rect(r);
 }
 
 gfx::Rect HWNDMessageHandler::GetClientAreaBoundsInScreen() const {
   RECT r;
-  GetClientRect(hwnd(), &r);
+  ::GetClientRect(hwnd(), &r);
   POINT point = {r.left, r.top};
-  ClientToScreen(hwnd(), &point);
+  ::ClientToScreen(hwnd(), &point);
   return gfx::Rect(point.x, point.y, r.right - r.left, r.bottom - r.top);
 }
 
@@ -560,7 +560,7 @@
       // GetWindowPlacement can return misleading position if a normalized
       // window was resized using Aero Snap feature (see comment 9 in bug
       // 36421). As a workaround, using GetWindowRect for normalized windows.
-      succeeded = GetWindowRect(hwnd(), &wp.rcNormalPosition) != 0;
+      succeeded = ::GetWindowRect(hwnd(), &wp.rcNormalPosition) != 0;
       DCHECK(succeeded);
 
       *bounds = gfx::Rect(wp.rcNormalPosition);
@@ -568,8 +568,8 @@
       MONITORINFO mi;
       mi.cbSize = sizeof(mi);
       succeeded =
-          GetMonitorInfo(MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONEAREST),
-                         &mi) != 0;
+          ::GetMonitorInfo(
+              ::MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONEAREST), &mi) != 0;
       DCHECK(succeeded);
 
       *bounds = gfx::Rect(wp.rcNormalPosition);
@@ -598,17 +598,17 @@
 }
 
 void HWNDMessageHandler::SetParentOrOwner(HWND new_parent) {
-  LONG style = GetWindowLong(hwnd(), GWL_STYLE);
+  LONG style = ::GetWindowLong(hwnd(), GWL_STYLE);
   if (style & WS_CHILD) {
     // This is a child window.
     // TODO(crbug.com/40284685): allows setting NULL parent since WinAPI permits
     // it. It will require updating window styles. See
     // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setparent#remarks.
     DCHECK(new_parent);
-    SetParent(hwnd(), new_parent);
+    ::SetParent(hwnd(), new_parent);
   } else {
-    SetWindowLongPtr(hwnd(), GWLP_HWNDPARENT,
-                     reinterpret_cast<LONG_PTR>(new_parent));
+    ::SetWindowLongPtr(hwnd(), GWLP_HWNDPARENT,
+                       reinterpret_cast<LONG_PTR>(new_parent));
   }
 }
 
@@ -638,7 +638,7 @@
     if (it != window_owner_map.end()) {
       for (HWND owned_child : it->second) {
         // The window could have been destroyed between EnumWindows and now.
-        if (IsWindow(owned_child)) {
+        if (::IsWindow(owned_child)) {
           owned_windows.push_back(owned_child);
           // Queue the current child for BFS exploration.
           to_process.push(owned_child);
@@ -666,13 +666,13 @@
 }
 
 void HWNDMessageHandler::SetSize(const gfx::Size& size) {
-  SetWindowPos(hwnd(), nullptr, 0, 0, size.width(), size.height(),
-               SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);
+  ::SetWindowPos(hwnd(), nullptr, 0, 0, size.width(), size.height(),
+                 SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);
 }
 
 void HWNDMessageHandler::CenterWindow(const gfx::Size& size) {
-  HWND parent = GetParent(hwnd());
-  if (!IsWindow(parent)) {
+  HWND parent = ::GetParent(hwnd());
+  if (!::IsWindow(parent)) {
     parent = ::GetWindow(hwnd(), GW_OWNER);
   }
   gfx::CenterAndSizeWindow(parent, hwnd(), size);
@@ -714,7 +714,7 @@
     placement.length = sizeof(WINDOWPLACEMENT);
     placement.showCmd = SW_SHOWMAXIMIZED;
     placement.rcNormalPosition = pixel_restore_bounds.ToRECT();
-    SetWindowPlacement(hwnd(), &placement);
+    ::SetWindowPlacement(hwnd(), &placement);
     native_show_state = SW_SHOWMAXIMIZED;
   } else {
     const bool is_maximized_or_arranged =
@@ -738,8 +738,8 @@
         native_show_state = SW_SHOWMINIMIZED;
         break;
       case ui::mojom::WindowShowState::kNormal:
-        if ((GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_TRANSPARENT) ||
-            (GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_NOACTIVATE)) {
+        if ((::GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_TRANSPARENT) ||
+            (::GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_NOACTIVATE)) {
           native_show_state =
               is_maximized_or_arranged ? SW_SHOWNA : SW_SHOWNOACTIVATE;
         } else {
@@ -756,7 +756,7 @@
         break;
     }
 
-    ShowWindow(hwnd(), native_show_state);
+    ::ShowWindow(hwnd(), native_show_state);
     // When launched from certain programs like bash and Windows Live
     // Messenger, show_state is set to SW_HIDE, so we need to correct that
     // condition. We don't just change show_state to SW_SHOWNORMAL because
@@ -765,7 +765,7 @@
     // ignored (!!#@@#!). Instead, we call ShowWindow again in this case.
     if (native_show_state == SW_HIDE) {
       native_show_state = SW_SHOWNORMAL;
-      ShowWindow(hwnd(), native_show_state);
+      ::ShowWindow(hwnd(), native_show_state);
     }
   }
 
@@ -784,14 +784,14 @@
 }
 
 void HWNDMessageHandler::Hide() {
-  if (IsWindow(hwnd())) {
+  if (::IsWindow(hwnd())) {
     // NOTE: Be careful not to activate any windows here (for example, calling
     // ShowWindow(SW_HIDE) will automatically activate another window).  This
     // code can be called while a window is being deactivated, and activating
     // another window will screw up the activation that is already in progress.
-    SetWindowPos(hwnd(), nullptr, 0, 0, 0, 0,
-                 SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE |
-                     SWP_NOREPOSITION | SWP_NOSIZE | SWP_NOZORDER);
+    ::SetWindowPos(hwnd(), nullptr, 0, 0, 0, 0,
+                   SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE |
+                       SWP_NOREPOSITION | SWP_NOSIZE | SWP_NOZORDER);
   }
 }
 
@@ -823,7 +823,7 @@
   }
 
   ::SetWindowPos(hwnd(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
-  SetForegroundWindow(hwnd());
+  ::SetForegroundWindow(hwnd());
 }
 
 void HWNDMessageHandler::Deactivate() {
@@ -863,7 +863,7 @@
 }
 
 bool HWNDMessageHandler::IsAlwaysOnTop() const {
-  return (GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_TOPMOST) != 0;
+  return (::GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_TOPMOST) != 0;
 }
 
 bool HWNDMessageHandler::IsHeadless() const {
@@ -872,15 +872,15 @@
 
 bool HWNDMessageHandler::RunMoveLoop(const gfx::Vector2d& drag_offset,
                                      bool hide_on_escape) {
-  ReleaseCapture();
+  ::ReleaseCapture();
   MoveLoopMouseWatcher watcher(msg_handler_weak_factory_.GetWeakPtr(),
                                hide_on_escape);
   // In Aura, we handle touch events asynchronously. So we need to allow nested
   // tasks while in windows move loop.
   base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
 
-  SendMessage(hwnd(), WM_SYSCOMMAND, SC_MOVE | 0x0002,
-              static_cast<LPARAM>(GetMessagePos()));
+  ::SendMessage(hwnd(), WM_SYSCOMMAND, SC_MOVE | 0x0002,
+                static_cast<LPARAM>(::GetMessagePos()));
   // Windows doesn't appear to offer a way to determine whether the user
   // canceled the move or not. We assume if the user released the mouse it was
   // successful.
@@ -888,14 +888,14 @@
 }
 
 void HWNDMessageHandler::EndMoveLoop() {
-  SendMessage(hwnd(), WM_CANCELMODE, 0, 0);
+  ::SendMessage(hwnd(), WM_CANCELMODE, 0, 0);
 }
 
 void HWNDMessageHandler::SendFrameChanged() {
-  SetWindowPos(hwnd(), nullptr, 0, 0, 0, 0,
-               SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE |
-                   SWP_NOOWNERZORDER | SWP_NOREPOSITION | SWP_NOSENDCHANGING |
-                   SWP_NOSIZE | SWP_NOZORDER);
+  ::SetWindowPos(hwnd(), nullptr, 0, 0, 0, 0,
+                 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOCOPYBITS |
+                     SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREPOSITION |
+                     SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOZORDER);
 }
 
 void HWNDMessageHandler::FlashFrame(bool flash) {
@@ -909,7 +909,7 @@
   } else {
     fwi.dwFlags = FLASHW_STOP;
   }
-  FlashWindowEx(&fwi);
+  ::FlashWindowEx(&fwi);
 }
 
 void HWNDMessageHandler::ClearNativeFocus() {
@@ -945,23 +945,23 @@
 
 void HWNDMessageHandler::SetVisibilityChangedAnimationsEnabled(bool enabled) {
   int dwm_value = enabled ? FALSE : TRUE;
-  DwmSetWindowAttribute(hwnd(), DWMWA_TRANSITIONS_FORCEDISABLED, &dwm_value,
-                        sizeof(dwm_value));
+  ::DwmSetWindowAttribute(hwnd(), DWMWA_TRANSITIONS_FORCEDISABLED, &dwm_value,
+                          sizeof(dwm_value));
 }
 
 bool HWNDMessageHandler::SetTitle(const std::u16string& title) {
   std::wstring current_title;
-  auto len_with_null = static_cast<size_t>(GetWindowTextLength(hwnd())) + 1;
+  auto len_with_null = static_cast<size_t>(::GetWindowTextLength(hwnd())) + 1;
   if (len_with_null == 1 && title.length() == 0) {
     return false;
   }
   if (len_with_null - 1 == title.length() &&
-      GetWindowText(hwnd(), base::WriteInto(&current_title, len_with_null),
-                    len_with_null) &&
+      ::GetWindowText(hwnd(), base::WriteInto(&current_title, len_with_null),
+                      static_cast<int>(len_with_null)) &&
       current_title == base::AsWStringView(title)) {
     return false;
   }
-  SetWindowText(hwnd(), base::as_wcstr(title));
+  ::SetWindowText(hwnd(), base::as_wcstr(title));
   return true;
 }
 
@@ -1000,25 +1000,25 @@
   if (!window_icon.isNull()) {
     base::win::ScopedGDIObject<HICON> previous_icon = std::move(window_icon_);
     window_icon_ = IconUtil::CreateHICONFromSkBitmapSizedTo(
-        *window_icon.bitmap(), GetSystemMetrics(SM_CXSMICON),
-        GetSystemMetrics(SM_CYSMICON));
-    SendMessage(hwnd(), WM_SETICON, ICON_SMALL,
-                reinterpret_cast<LPARAM>(window_icon_.get()));
+        *window_icon.bitmap(), ::GetSystemMetrics(SM_CXSMICON),
+        ::GetSystemMetrics(SM_CYSMICON));
+    ::SendMessage(hwnd(), WM_SETICON, ICON_SMALL,
+                  reinterpret_cast<LPARAM>(window_icon_.get()));
   }
   if (!app_icon.isNull()) {
     base::win::ScopedGDIObject<HICON> previous_icon = std::move(app_icon_);
     app_icon_ = IconUtil::CreateHICONFromSkBitmapSizedTo(
-        *app_icon.bitmap(), GetSystemMetrics(SM_CXICON),
-        GetSystemMetrics(SM_CYICON));
-    SendMessage(hwnd(), WM_SETICON, ICON_BIG,
-                reinterpret_cast<LPARAM>(app_icon_.get()));
+        *app_icon.bitmap(), ::GetSystemMetrics(SM_CXICON),
+        ::GetSystemMetrics(SM_CYICON));
+    ::SendMessage(hwnd(), WM_SETICON, ICON_BIG,
+                  reinterpret_cast<LPARAM>(app_icon_.get()));
   }
 }
 
 void HWNDMessageHandler::SetFullscreen(bool fullscreen,
                                        int64_t target_display_id) {
   // Erase any prior reference to this window in the fullscreen window map.
-  HMONITOR monitor = MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY);
+  HMONITOR monitor = ::MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY);
   FullscreenWindowMonitorMap::iterator iter =
       fullscreen_monitor_map_.Get().find(monitor);
   if (iter != fullscreen_monitor_map_.Get().end()) {
@@ -1034,7 +1034,8 @@
 
   // Add an entry in the fullscreen window map if the window is now fullscreen.
   if (fullscreen) {
-    HMONITOR new_monitor = MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY);
+    HMONITOR new_monitor =
+        ::MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY);
     (fullscreen_monitor_map_.Get())[new_monitor] = this;
   }
   // If we are out of fullscreen and there was a pending DWM transition for the
@@ -1055,7 +1056,7 @@
   // When the aspect ratio is set, size the window to adhere to it. This keeps
   // the same origin point as the original window.
   RECT window_rect;
-  if (GetWindowRect(hwnd(), &window_rect)) {
+  if (::GetWindowRect(hwnd(), &window_rect)) {
     gfx::Rect rect(window_rect);
 
     SizeWindowToAspectRatio(WMSZ_BOTTOMRIGHT, &rect);
@@ -1064,7 +1065,7 @@
 }
 
 void HWNDMessageHandler::SizeConstraintsChanged() {
-  LONG style = GetWindowLong(hwnd(), GWL_STYLE);
+  LONG style = ::GetWindowLong(hwnd(), GWL_STYLE);
 
   // Key style considerations:
   // - WS_THICKFRAME: Enables resizing. Cannot be used with translucent
@@ -1091,7 +1092,7 @@
   set_style_func(WS_MAXIMIZEBOX, can_maximize);
   set_style_func(WS_MINIMIZEBOX, delegate_->CanMinimize());
 
-  SetWindowLong(hwnd(), GWL_STYLE, style);
+  ::SetWindowLong(hwnd(), GWL_STYLE, style);
   SendFrameChanged();
 }
 
@@ -1178,7 +1179,7 @@
   msg_handled_ = old_msg_handled;
 
   if (!processed) {
-    result = DefWindowProc(window, message, w_param, l_param);
+    result = ::DefWindowProc(window, message, w_param, l_param);
     // DefWindowProc() may have destroyed the window and/or us in a nested
     // message loop.
     if (!ref || !::IsWindow(window)) {
@@ -1316,7 +1317,7 @@
 
 void HWNDMessageHandler::ApplyPinchZoomScale(float scale) {
   POINT cursor_pos = GetCursorPos();
-  ScreenToClient(hwnd(), &cursor_pos);
+  ::ScreenToClient(hwnd(), &cursor_pos);
 
   ui::GestureEventDetails event_details(ui::EventType::kGesturePinchUpdate);
   event_details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHPAD);
@@ -1329,7 +1330,7 @@
 
 void HWNDMessageHandler::ApplyPinchZoomBegin() {
   POINT cursor_pos = GetCursorPos();
-  ScreenToClient(hwnd(), &cursor_pos);
+  ::ScreenToClient(hwnd(), &cursor_pos);
 
   ui::GestureEventDetails event_details(ui::EventType::kGesturePinchBegin);
   event_details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHPAD);
@@ -1341,7 +1342,7 @@
 
 void HWNDMessageHandler::ApplyPinchZoomEnd() {
   POINT cursor_pos = GetCursorPos();
-  ScreenToClient(hwnd(), &cursor_pos);
+  ::ScreenToClient(hwnd(), &cursor_pos);
 
   ui::GestureEventDetails event_details(ui::EventType::kGesturePinchEnd);
   event_details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHPAD);
@@ -1361,7 +1362,7 @@
   POINT root_location = GetCursorPos();
 
   POINT location = {root_location.x, root_location.y};
-  ScreenToClient(hwnd(), &location);
+  ::ScreenToClient(hwnd(), &location);
 
   gfx::Point cursor_location(location);
   gfx::Point cursor_root_location(root_location);
@@ -1472,19 +1473,19 @@
 void HWNDMessageHandler::OnAppbarAutohideEdgesChanged() {
   // This triggers querying WM_NCCALCSIZE again.
   RECT client;
-  GetWindowRect(hwnd(), &client);
+  ::GetWindowRect(hwnd(), &client);
 
   // Add SWP_NOZORDER and SWP_NOACTIVATE flags to SetWindowPos to preserve the
   // correct Z-order after restarting maximized browsers. Without these flags,
   // SetWindowPos would always bring the current window to the top.
-  SetWindowPos(hwnd(), nullptr, client.left, client.top,
-               client.right - client.left, client.bottom - client.top,
-               SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE);
+  ::SetWindowPos(hwnd(), nullptr, client.left, client.top,
+                 client.right - client.left, client.bottom - client.top,
+                 SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE);
 }
 
 void HWNDMessageHandler::SetInitialFocus() {
-  if (!(GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_TRANSPARENT) &&
-      !(GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_NOACTIVATE)) {
+  if (!(::GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_TRANSPARENT) &&
+      !(::GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_NOACTIVATE)) {
     // The window does not get keyboard messages unless we focus it.
     // Headless windows don't get native focus, so just pretend we grabbed one.
     if (IsHeadless()) {
@@ -1546,10 +1547,10 @@
   // active is on the same monitor as the fullscreen window.
   if (!active) {
     if (IsFullscreen() && ::IsWindow(window_gaining_or_losing_activation)) {
-      HMONITOR active_window_monitor = MonitorFromWindow(
+      HMONITOR active_window_monitor = ::MonitorFromWindow(
           window_gaining_or_losing_activation, MONITOR_DEFAULTTOPRIMARY);
       HMONITOR fullscreen_window_monitor =
-          MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY);
+          ::MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY);
 
       if (active_window_monitor == fullscreen_window_monitor) {
         OnBackgroundFullscreen();
@@ -1559,8 +1560,8 @@
     // Restore the bounds of the window to fullscreen.
     DCHECK(IsFullscreen());
     MONITORINFO monitor_info = {sizeof(monitor_info)};
-    GetMonitorInfo(MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY),
-                   &monitor_info);
+    ::GetMonitorInfo(::MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY),
+                     &monitor_info);
     SetBoundsInternal(gfx::Rect(monitor_info.rcMonitor), false);
     // Inform the taskbar that this window is now a fullscreen window so it go
     // behind the window in the Z-Order. The taskbar heuristics to detect
@@ -1593,7 +1594,7 @@
 
 void HWNDMessageHandler::ExecuteSystemMenuCommand(int command) {
   if (command) {
-    SendMessage(hwnd(), WM_SYSCOMMAND, static_cast<WPARAM>(command), 0);
+    ::SendMessage(hwnd(), WM_SYSCOMMAND, static_cast<WPARAM>(command), 0);
   }
 }
 
@@ -1642,7 +1643,7 @@
 bool HWNDMessageHandler::GetClientAreaInsets(gfx::Insets* insets,
                                              HMONITOR monitor) const {
   int frame_thickness = ui::GetResizableFrameThicknessFromMonitorInPixels(
-      monitor, GetWindowLong(hwnd(), GWL_STYLE) & WS_CAPTION);
+      monitor, ::GetWindowLong(hwnd(), GWL_STYLE) & WS_CAPTION);
   if (delegate_->GetClientAreaInsets(insets, frame_thickness)) {
     return true;
   }
@@ -1689,17 +1690,17 @@
   GetWindowRgn(hwnd(), current_rgn.get());
 
   RECT window_rect;
-  GetWindowRect(hwnd(), &window_rect);
+  ::GetWindowRect(hwnd(), &window_rect);
   base::win::ScopedGDIObject<HRGN> new_region;
   if (custom_window_region_.is_valid()) {
     new_region.reset(CreateRectRgn(0, 0, 0, 0));
     CombineRgn(new_region.get(), custom_window_region_.get(), nullptr,
                RGN_COPY);
   } else if (IsMaximized()) {
-    HMONITOR monitor = MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONEAREST);
+    HMONITOR monitor = ::MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONEAREST);
     MONITORINFO mi;
     mi.cbSize = sizeof mi;
-    GetMonitorInfo(monitor, &mi);
+    ::GetMonitorInfo(monitor, &mi);
     RECT work_rect = mi.rcWork;
     OffsetRect(&work_rect, static_cast<int>(-window_rect.left),
                static_cast<int>(-window_rect.top));
@@ -1730,7 +1731,7 @@
   // The Widget and HWND can be destroyed in the call to DefWindowProc, so use
   // the WeakPtrFactory to avoid unlocking (and crashing) after destruction.
   base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr());
-  LRESULT result = DefWindowProc(hwnd(), message, w_param, l_param);
+  LRESULT result = ::DefWindowProc(hwnd(), message, w_param, l_param);
   if (!ref) {
     lock.CancelUnlockOperation();
   }
@@ -1739,15 +1740,15 @@
 
 void HWNDMessageHandler::LockUpdates() {
   if (++lock_updates_count_ == 1) {
-    SetWindowLong(hwnd(), GWL_STYLE,
-                  GetWindowLong(hwnd(), GWL_STYLE) & ~WS_VISIBLE);
+    ::SetWindowLong(hwnd(), GWL_STYLE,
+                    ::GetWindowLong(hwnd(), GWL_STYLE) & ~WS_VISIBLE);
   }
 }
 
 void HWNDMessageHandler::UnlockUpdates() {
   if (--lock_updates_count_ <= 0) {
-    SetWindowLong(hwnd(), GWL_STYLE,
-                  GetWindowLong(hwnd(), GWL_STYLE) | WS_VISIBLE);
+    ::SetWindowLong(hwnd(), GWL_STYLE,
+                    ::GetWindowLong(hwnd(), GWL_STYLE) | WS_VISIBLE);
     lock_updates_count_ = 0;
   }
 }
@@ -1766,7 +1767,7 @@
         base::Milliseconds(500));
     return;
   }
-  InvalidateRect(hwnd(), nullptr, FALSE);
+  ::InvalidateRect(hwnd(), nullptr, FALSE);
 }
 
 bool HWNDMessageHandler::IsFrameSystemDrawn() const {
@@ -1840,8 +1841,8 @@
   if (base::win::GetVersion() >= base::win::Version::WIN11 &&
       use_rounded_corner_) {
     DWM_WINDOW_CORNER_PREFERENCE corner_pref = DWMWCP_ROUND;
-    DwmSetWindowAttribute(hwnd(), DWMWA_WINDOW_CORNER_PREFERENCE, &corner_pref,
-                          sizeof(corner_pref));
+    ::DwmSetWindowAttribute(hwnd(), DWMWA_WINDOW_CORNER_PREFERENCE,
+                            &corner_pref, sizeof(corner_pref));
   }
 
   fullscreen_handler_->set_hwnd(hwnd());
@@ -1850,17 +1851,17 @@
 
   // This message initializes the window so that focus border are shown for
   // windows.
-  SendMessage(hwnd(), WM_CHANGEUISTATE, MAKELPARAM(UIS_CLEAR, UISF_HIDEFOCUS),
-              0);
+  ::SendMessage(hwnd(), WM_CHANGEUISTATE, MAKELPARAM(UIS_CLEAR, UISF_HIDEFOCUS),
+                0);
 
   if (!delegate_->HasFrame()) {
-    SetWindowLong(hwnd(), GWL_STYLE,
-                  GetWindowLong(hwnd(), GWL_STYLE) & ~WS_CAPTION);
+    ::SetWindowLong(hwnd(), GWL_STYLE,
+                    ::GetWindowLong(hwnd(), GWL_STYLE) & ~WS_CAPTION);
     SendFrameChanged();
   }
 
   // Get access to a modifiable copy of the system menu.
-  GetSystemMenu(hwnd(), false);
+  ::GetSystemMenu(hwnd(), false);
 
   // We need to allow the delegate to size its contents since the window may not
   // receive a size notification when its initial bounds are specified at window
@@ -2029,7 +2030,7 @@
     // minimize/maximize/close buttons.
     needs_dwm_frame_clear_ = false;
     RECT client_rect;
-    GetClientRect(hwnd(), &client_rect);
+    ::GetClientRect(hwnd(), &client_rect);
     base::win::ScopedGDIObject<HBRUSH> brush(CreateSolidBrush(0));
     // The DC and GetClientRect operate in client area coordinates.
     RECT rect = {0, 0, client_rect.right, insets.top()};
@@ -2066,8 +2067,8 @@
   // view reports its size as the client size.
   if (delegate_->WidgetSizeIsClientSize()) {
     RECT client_rect, window_rect;
-    GetClientRect(hwnd(), &client_rect);
-    GetWindowRect(hwnd(), &window_rect);
+    ::GetClientRect(hwnd(), &client_rect);
+    ::GetWindowRect(hwnd(), &window_rect);
     CR_DEFLATE_RECT(&window_rect, &client_rect);
     min_window_size.Enlarge(window_rect.right - window_rect.left,
                             window_rect.bottom - window_rect.top);
@@ -2083,10 +2084,10 @@
   minmax_info->ptMinTrackSize.y = min_window_size.height();
   if (max_window_size.width() || max_window_size.height()) {
     if (!max_window_size.width()) {
-      max_window_size.set_width(GetSystemMetrics(SM_CXMAXTRACK));
+      max_window_size.set_width(::GetSystemMetrics(SM_CXMAXTRACK));
     }
     if (!max_window_size.height()) {
-      max_window_size.set_height(GetSystemMetrics(SM_CYMAXTRACK));
+      max_window_size.set_height(::GetSystemMetrics(SM_CYMAXTRACK));
     }
     minmax_info->ptMaxTrackSize.x = max_window_size.width();
     minmax_info->ptMaxTrackSize.y = max_window_size.height();
@@ -2244,7 +2245,7 @@
     }
     return MA_NOACTIVATEANDEAT;
   }
-  if (GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_NOACTIVATE) {
+  if (::GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_NOACTIVATE) {
     return MA_NOACTIVATE;
   }
   SetMsgHandled(FALSE);
@@ -2352,7 +2353,7 @@
 
     POINT cursor_pos = {0};
     ::GetCursorPos(&cursor_pos);
-    ScreenToClient(hwnd(), &cursor_pos);
+    ::ScreenToClient(hwnd(), &cursor_pos);
     ui::MouseEvent event(
         ui::EventType::kMouseMoved, gfx::PointF(cursor_pos.x, cursor_pos.y),
         gfx::PointF(cursor_pos.x, cursor_pos.y), ui::EventTimeForNow(),
@@ -2441,7 +2442,7 @@
   // See http://code.google.com/p/chromium/issues/detail?id=900
   if (is_first_nccalc_) {
     is_first_nccalc_ = false;
-    if (GetWindowLong(hwnd(), GWL_STYLE) & WS_CAPTION) {
+    if (::GetWindowLong(hwnd(), GWL_STYLE) & WS_CAPTION) {
       SetMsgHandled(FALSE);
       return 0;
     }
@@ -2451,7 +2452,7 @@
       mode ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param)->rgrc[0])
            : reinterpret_cast<RECT*>(l_param);
 
-  HMONITOR monitor = MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONULL);
+  HMONITOR monitor = ::MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONULL);
   if (!monitor) {
     // We might end up here if the window was previously minimized and the
     // user clicks on the taskbar button to restore it in the previous
@@ -2586,14 +2587,14 @@
 
   // Otherwise, we let Windows do all the native frame non-client handling for
   // us.
-  LRESULT hit_test_code =
-      DefWindowProc(hwnd(), WM_NCHITTEST, 0, MAKELPARAM(point.x(), point.y()));
+  LRESULT hit_test_code = ::DefWindowProc(hwnd(), WM_NCHITTEST, 0,
+                                          MAKELPARAM(point.x(), point.y()));
   return hit_test_code;
 }
 
 void HWNDMessageHandler::OnNCPaint(HRGN rgn) {
   RECT window_rect;
-  GetWindowRect(hwnd(), &window_rect);
+  ::GetWindowRect(hwnd(), &window_rect);
   RECT dirty_region;
   // A value of 1 indicates paint all.
   if (!rgn || rgn == reinterpret_cast<HRGN>(1)) {
@@ -2721,7 +2722,7 @@
       // isn't needed if we've just cleared the whole client area outside the
       // child window above.
       RECT cr;
-      if (GetClientRect(hwnd(), &cr)) {
+      if (::GetClientRect(hwnd(), &cr)) {
         // GetClientRect() always returns a rect with top/left at 0.
         const gfx::Size client_area = gfx::Rect(cr).size();
 
@@ -2841,7 +2842,7 @@
 }
 
 void HWNDMessageHandler::OnSettingChange(UINT flags, const wchar_t* section) {
-  if (!GetParent(hwnd()) && (flags == SPI_SETWORKAREA)) {
+  if (!::GetParent(hwnd()) && (flags == SPI_SETWORKAREA)) {
     // Fire a dummy SetWindowPos() call, so we'll trip the code in
     // OnWindowPosChanging() below that notices work area changes.
     ::SetWindowPos(hwnd(), nullptr, 0, 0, 0, 0,
@@ -2930,8 +2931,8 @@
     if (window_bounds_change && !IsVisible()) {
       // Circumvent ScopedRedrawLocks and force visibility before entering a
       // resize or move modal loop to get continuous sizing/moving feedback.
-      SetWindowLong(hwnd(), GWL_STYLE,
-                    GetWindowLong(hwnd(), GWL_STYLE) | WS_VISIBLE);
+      ::SetWindowLong(hwnd(), GWL_STYLE,
+                      ::GetWindowLong(hwnd(), GWL_STYLE) | WS_VISIBLE);
     }
   }
 
@@ -2978,8 +2979,8 @@
   // situation.
   base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
   // If the delegate can't handle it, the system implementation will be called.
-  DefWindowProc(hwnd(), WM_SYSCOMMAND, notification_code,
-                MAKELPARAM(point.x(), point.y()));
+  ::DefWindowProc(hwnd(), WM_SYSCOMMAND, notification_code,
+                  MAKELPARAM(point.x(), point.y()));
   if (is_mouse_menu && ref) {
     handling_mouse_menu_ = false;
   }
@@ -3021,7 +3022,7 @@
       window_pos->flags &=
           static_cast<unsigned int>(~(SWP_SHOWWINDOW | SWP_HIDEWINDOW));
     }
-  } else if (!GetParent(hwnd())) {
+  } else if (!::GetParent(hwnd())) {
     RECT window_rect;
     const bool have_new_window_rect =
         !(window_pos->flags & SWP_NOMOVE) && !(window_pos->flags & SWP_NOSIZE);
@@ -3039,7 +3040,7 @@
 
     HMONITOR monitor;
     gfx::Rect monitor_rect, work_area;
-    if ((have_new_window_rect || GetWindowRect(hwnd(), &window_rect)) &&
+    if ((have_new_window_rect || ::GetWindowRect(hwnd(), &window_rect)) &&
         GetMonitorAndRects(window_rect, &monitor, &monitor_rect, &work_area)) {
       bool work_area_changed = (monitor_rect == last_monitor_rect_) &&
                                (work_area != last_work_area_);
@@ -3128,7 +3129,7 @@
 
   RECT window_rect;
   gfx::Size old_size;
-  if (GetWindowRect(hwnd(), &window_rect)) {
+  if (::GetWindowRect(hwnd(), &window_rect)) {
     old_size = gfx::Rect(window_rect).size();
   }
   gfx::Size new_size = gfx::Size(window_pos->cx, window_pos->cy);
@@ -3144,8 +3145,8 @@
     // It's possible that if Aero snap is being entered then the window size
     // won't actually change. Post a message to ensure swaps will be re-enabled
     // in that case.
-    PostMessage(hwnd(), WM_WINDOWSIZINGFINISHED, ++current_window_size_message_,
-                0);
+    ::PostMessage(hwnd(), WM_WINDOWSIZINGFINISHED,
+                  ++current_window_size_message_, 0);
     // Copying the old bits can sometimes cause a flash of black when
     // resizing. See https://crbug.com/739724
     if (is_translucent_) {
@@ -3255,7 +3256,7 @@
   // messages if it thinks the touch point is in non-client space.
   if (message != WM_MOUSEWHEEL && message != WM_MOUSEHWHEEL &&
       ui::IsMouseEventFromTouch(message)) {
-    LRESULT hittest = SendMessage(hwnd(), WM_NCHITTEST, 0, l_param);
+    LRESULT hittest = ::SendMessage(hwnd(), WM_NCHITTEST, 0, l_param);
     // Always DefWindowProc on the titlebar. We could let the event fall through
     // and the special handling in HandleMouseInputForCaption would take care of
     // this, but in the touch case Windows does a better job.
@@ -3294,12 +3295,12 @@
     // TODO(pkasting): Maybe handle this in DesktopWindowTreeHostWin, where we
     // handle alt-space, or in the frame itself.
     is_right_mouse_pressed_on_caption_ = false;
-    ReleaseCapture();
+    ::ReleaseCapture();
     // |point| is in window coordinates, but WM_NCHITTEST and TrackPopupMenu()
     // expect screen coordinates.
     POINT screen_point = CR_POINT_INITIALIZER_FROM_LPARAM(l_param);
     MapWindowPoints(hwnd(), HWND_DESKTOP, &screen_point, 1);
-    w_param = static_cast<WPARAM>(SendMessage(
+    w_param = static_cast<WPARAM>(::SendMessage(
         hwnd(), WM_NCHITTEST, 0, MAKELPARAM(screen_point.x, screen_point.y)));
     if (w_param == HTCAPTION || w_param == HTSYSMENU) {
       ShowSystemMenuAtScreenPixelLocation(hwnd(), gfx::Point(screen_point));
@@ -3520,7 +3521,7 @@
   }
 
   POINT client_point = pointer_info.ptPixelLocationRaw;
-  ScreenToClient(hwnd(), &client_point);
+  ::ScreenToClient(hwnd(), &client_point);
   gfx::Point touch_point = gfx::Point(client_point.x, client_point.y);
   ui::EventType event_type = GetTouchEventType(pointer_flags);
   const base::TimeTicks event_time = ui::EventTimeForNow();
@@ -3575,7 +3576,7 @@
       // Messages on HTCAPTION should be DefWindowProc'ed, as we let Windows
       // take care of dragging the window and double-tapping to maximize.
       const bool on_titlebar =
-          SendMessage(hwnd(), WM_NCHITTEST, 0, l_param) == HTCAPTION;
+          ::SendMessage(hwnd(), WM_NCHITTEST, 0, l_param) == HTCAPTION;
       // Unlike above, we must mark both WM_POINTERUP and WM_NCPOINTERUP as
       // handled, in order for the custom caption buttons to work correctly.
       if (event_type == ui::EventType::kTouchReleased && !on_titlebar) {
@@ -3592,7 +3593,7 @@
     UINT32 pointer_id,
     POINTER_PEN_INFO pointer_pen_info) {
   POINT client_point = pointer_pen_info.pointerInfo.ptPixelLocationRaw;
-  ScreenToClient(hwnd(), &client_point);
+  ::ScreenToClient(hwnd(), &client_point);
   gfx::Point point = gfx::Point(client_point.x, client_point.y);
 
   std::unique_ptr<ui::Event> event = pen_processor_.GenerateEvent(
@@ -3769,7 +3770,7 @@
         if (delegate_->GetFrameMode() == FrameMode::CUSTOM_DRAWN) {
           DefWindowProcWithRedrawLock(WM_NCLBUTTONDOWN, HTCAPTION, l_param);
         } else {
-          DefWindowProc(hwnd(), WM_NCLBUTTONDOWN, HTCAPTION, l_param);
+          ::DefWindowProc(hwnd(), WM_NCLBUTTONDOWN, HTCAPTION, l_param);
         }
       }
       break;
@@ -3795,9 +3796,9 @@
                                            bool force_size_changed) {
   gfx::Size old_size = GetClientAreaBounds().size();
 
-  SetWindowPos(hwnd(), nullptr, bounds_in_pixels.x(), bounds_in_pixels.y(),
-               bounds_in_pixels.width(), bounds_in_pixels.height(),
-               SWP_NOACTIVATE | SWP_NOZORDER);
+  ::SetWindowPos(hwnd(), nullptr, bounds_in_pixels.x(), bounds_in_pixels.y(),
+                 bounds_in_pixels.width(), bounds_in_pixels.height(),
+                 SWP_NOACTIVATE | SWP_NOZORDER);
 
   // If HWND size is not changed, we will not receive standard size change
   // notifications. If |force_size_changed| is |true|, we should pretend size is
@@ -3815,7 +3816,7 @@
 
 void HWNDMessageHandler::CheckAndHandleBackgroundFullscreenOnMonitor(
     HWND window) {
-  HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
+  HMONITOR monitor = ::MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
 
   FullscreenWindowMonitorMap::iterator iter =
       fullscreen_monitor_map_.Get().find(monitor);
@@ -3832,8 +3833,8 @@
   // Reduce the bounds of the window by 1px to ensure that Windows does
   // not treat this like a fullscreen window.
   MONITORINFO monitor_info = {sizeof(monitor_info)};
-  GetMonitorInfo(MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY),
-                 &monitor_info);
+  ::GetMonitorInfo(::MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY),
+                   &monitor_info);
   gfx::Rect shrunk_rect(monitor_info.rcMonitor);
   shrunk_rect.set_height(shrunk_rect.height() - 1);
   background_fullscreen_hack_ = true;
@@ -3890,7 +3891,7 @@
 }
 
 void HWNDMessageHandler::UpdateFullscreenMonitorMap() {
-  HMONITOR hmonitor = MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONULL);
+  HMONITOR hmonitor = ::MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONULL);
   if (!hmonitor) {
     // A null `hmonitor` indicates that the monitor where the current window
     // resides has been disconnected. Remove the HMONITOR corresponding to the
@@ -3942,13 +3943,13 @@
   DCHECK(monitor);
   DCHECK(monitor_rect);
   DCHECK(work_area);
-  *monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
+  *monitor = ::MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
   if (!*monitor) {
     return false;
   }
   MONITORINFO monitor_info = {0};
   monitor_info.cbSize = sizeof(monitor_info);
-  GetMonitorInfo(*monitor, &monitor_info);
+  ::GetMonitorInfo(*monitor, &monitor_info);
   *monitor_rect = gfx::Rect(monitor_info.rcMonitor);
   *work_area = gfx::Rect(monitor_info.rcWork);
   return true;
diff --git a/ui/views/win/scoped_fullscreen_visibility.cc b/ui/views/win/scoped_fullscreen_visibility.cc
index 479a809..10b01508 100644
--- a/ui/views/win/scoped_fullscreen_visibility.cc
+++ b/ui/views/win/scoped_fullscreen_visibility.cc
@@ -27,9 +27,9 @@
     // ShowWindow(SW_HIDE) will automatically activate another window).  This
     // code can be called while a window is being deactivated, and activating
     // another window will screw up the activation that is already in progress.
-    SetWindowPos(hwnd_, nullptr, 0, 0, 0, 0,
-                 SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE |
-                     SWP_NOREPOSITION | SWP_NOSIZE | SWP_NOZORDER);
+    ::SetWindowPos(hwnd_, nullptr, 0, 0, 0, 0,
+                   SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE |
+                       SWP_NOREPOSITION | SWP_NOSIZE | SWP_NOZORDER);
   }
 }
 
@@ -38,7 +38,7 @@
   CHECK(it != full_screen_windows_->end());
   if (--it->second == 0) {
     full_screen_windows_->erase(it);
-    ShowWindow(hwnd_, SW_SHOW);
+    ::ShowWindow(hwnd_, SW_SHOW);
   }
   if (full_screen_windows_->empty()) {
     delete full_screen_windows_;
diff --git a/ui/webui/resources/cr_components/composebox/composebox.ts b/ui/webui/resources/cr_components/composebox/composebox.ts
index 8b82ea8..96a7b2f8 100644
--- a/ui/webui/resources/cr_components/composebox/composebox.ts
+++ b/ui/webui/resources/cr_components/composebox/composebox.ts
@@ -261,6 +261,7 @@
         reflect: true,
       },
       isFollowupQuery: {type: Boolean},
+      shouldShowGhostFiles: {type: Boolean},
     };
   }
 
@@ -285,6 +286,12 @@
   accessor disableVoiceSearchAnimation: boolean = false;
   protected accessor tabSuggestions_: TabInfo[] = [];
   accessor lensButtonDisabled: boolean = false;
+  // Set this to true in parent component if it is desired
+  // to show files that are not in the file map when
+  // file status is updated from backend. Ghost files will be
+  // shown as image chip with spinner in file carousel.
+  accessor shouldShowGhostFiles: boolean = false;
+
   protected composeboxNoFlickerSuggestionsFix_: boolean =
       loadTimeData.getBoolean('composeboxNoFlickerSuggestionsFix');
   // If isCollapsible is set to true, the composebox will be a pill shape until
@@ -737,6 +744,11 @@
     this.updateInputPlaceholder_();
   }
 
+  protected addToPendingUploads_(token: UnguessableToken) {
+    this.pendingUploads_.add(token);
+    this.fileUploadsComplete = false;
+  }
+
   protected computeCancelButtonTitle_() {
     return this.input_.trim().length > 0 || this.files_.size > 0 ?
         this.i18n('composeboxCancelButtonTitleInput') :
@@ -909,6 +921,9 @@
         ComposeboxContextAddedMethod.CONTEXT_MENU, this.composeboxSource_);
   }
 
+  // Start file upload flow from frontend. This contrasts with
+  // `onFileContextAdded_`, which is for the file upload flow that is started
+  // from the backend.
   private async addFileContext_(files: File[]) {
     const composeboxFiles: Map<UnguessableToken, ComposeboxFile> = new Map();
     for (const file of files) {
@@ -1753,8 +1768,7 @@
         // `NotUploaded`, `UploadStarted` come before and after `kProcessing`
         //  respectively, so we only need to add to `pendingUploads_` when in a
         //  type of processing state.
-        this.pendingUploads_.add(file.uuid);
-        this.fileUploadsComplete = this.pendingUploads_.size === 0;
+        this.addToPendingUploads_(file.uuid);
       }
 
       // Fetch contextual suggestions for processingSuggestSignalsReady
@@ -1866,6 +1880,10 @@
     return this.input_;
   }
 
+  getNumOfFilesForTesting(): number {
+    return this.files_.size;
+  }
+
   private selectFirstMatch() {
     if (this.result_?.matches.length) {
       this.$.matches.selectFirst();
@@ -1908,10 +1926,15 @@
     }
   }
 
+  // This function is called when backend starts a file upload flow, whether
+  // through `addFileFromAttachment_`, `addFileContextFromBrowser`, etc. This
+  // contrasts with the workflows where the frontend starts a file upload flow
+  // (`addFileContext_`).
   private onFileContextAdded_(file: ComposeboxFile) {
     const newFiles = new Map(this.files_);
     newFiles.set(file.uuid, file);
     this.files_ = newFiles;
+    this.addToPendingUploads_(file.uuid);
   }
 
   private handleProcessFilesError_(error: ProcessFilesError) {
@@ -2008,20 +2031,25 @@
       }
       this.files_ = new Map([...this.files_]);
     } else {
-      // File is unknown but its status is known. Add it to file carousel
-      // while we wait for its file details to be known.
-      if (this.entrypointName === 'Omnibox') {
+      // File is unknown but its status is known. Show this if
+      // ghost/unknown files in frontend are allowed to be in
+      // carousel.
+      if (this.shouldShowGhostFiles) {
         file = {
           uuid: token,
           name: '',
           objectUrl: null,
           dataUrl: null,
           type: '',
-          status: status,
+          // Override this since first upload status is this or processing.
+          // Need this or processing in order to show tab spinner.
+          status: FileUploadStatus.kUploadStarted,
           url: null,
           tabId: null,
           isDeletable: true,
         };
+        // Update pending uploads in 'composebox.ts' to disable
+        // submit button.
         this.onFileContextAdded_(file);
       }
     }
diff --git a/ui/webui/resources/cr_elements/BUILD.gn b/ui/webui/resources/cr_elements/BUILD.gn
index cd766aa..87d5db6 100644
--- a/ui/webui/resources/cr_elements/BUILD.gn
+++ b/ui/webui/resources/cr_elements/BUILD.gn
@@ -88,8 +88,7 @@
   }
 
   if ((!is_android && !is_ios) || is_desktop_android) {
-    web_component_files += [ "cr_a11y_announcer/cr_a11y_announcer.ts" ]
-
+    static_files = [ "cr_a11y_announcer/cr_a11y_announcer.css" ]
     ts_files += [
       "cr_radio_button/cr_radio_button_mixin_lit.ts",
       "cr_search_field/cr_search_field_mixin_lit.ts",
@@ -99,6 +98,8 @@
       # Web components files that either
       #  - don't have a corresponding .html file or
       #  - have a checked-in *.html.ts wrapper file instead of auto-generated
+      "cr_a11y_announcer/cr_a11y_announcer.html.ts",
+      "cr_a11y_announcer/cr_a11y_announcer.ts",
       "cr_collapse/cr_collapse.html.ts",
       "cr_collapse/cr_collapse.ts",
       "cr_drawer/cr_drawer.html.ts",
diff --git a/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.css b/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.css
new file mode 100644
index 0000000..de42a14
--- /dev/null
+++ b/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.css
@@ -0,0 +1,11 @@
+/* Copyright 2026 The Chromium Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+:host {
+  clip: rect(0 0 0 0);
+  height: 1px;
+  overflow: hidden;
+  position: fixed;
+  width: 1px;
+}
diff --git a/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.html b/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.html
deleted file mode 100644
index b0a892d..0000000
--- a/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<style>
-  :host {
-    clip: rect(0 0 0 0);
-    height: 1px;
-    overflow: hidden;
-    position: fixed;
-    width: 1px;
-  }
-</style>
-
-<div id="messages" role="alert" aria-live="polite" aria-relevant="additions">
-</div>
diff --git a/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.html.ts b/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.html.ts
new file mode 100644
index 0000000..cacc0a6a
--- /dev/null
+++ b/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.html.ts
@@ -0,0 +1,11 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {getTrustedHTML} from '//resources/js/static_types.js';
+
+export function getTemplate() {
+  return getTrustedHTML`
+<div id="messages" role="alert" aria-live="polite" aria-relevant="additions">
+</div>`;
+}
diff --git a/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.ts b/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.ts
index 723cbc5..7cd09b1 100644
--- a/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.ts
+++ b/ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.ts
@@ -5,6 +5,7 @@
 import {assert} from '//resources/js/assert.js';
 import {CustomElement} from '//resources/js/custom_element.js';
 
+import sheet from './cr_a11y_announcer.css' with {type : 'css'};
 import {getTemplate} from './cr_a11y_announcer.html.js';
 
 /**
@@ -62,6 +63,11 @@
   private currentTimeout_: number|null = null;
   private messages_: string[] = [];
 
+  constructor() {
+    super();
+    this.shadowRoot!.adoptedStyleSheets = [sheet];
+  }
+
   disconnectedCallback() {
     if (this.currentTimeout_ !== null) {
       clearTimeout(this.currentTimeout_);
diff --git a/ui/webui/resources/cr_elements/cr_icon/cr_iconset.html.ts b/ui/webui/resources/cr_elements/cr_icon/cr_iconset.html.ts
index b24f1db..acdac3a 100644
--- a/ui/webui/resources/cr_elements/cr_icon/cr_iconset.html.ts
+++ b/ui/webui/resources/cr_elements/cr_icon/cr_iconset.html.ts
@@ -10,8 +10,7 @@
   return html`
 <svg id="baseSvg" xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 ${this.size} ${this.size}"
-     preserveAspectRatio="xMidYMid meet" focusable="false"
-     style="pointer-events: none; display: block; width: 100%; height: 100%;">
+     preserveAspectRatio="xMidYMid meet" focusable="false">
  </svg>
 <slot></slot>
 `;
diff --git a/ui/webui/resources/cr_elements/cr_icon/cr_iconset.ts b/ui/webui/resources/cr_elements/cr_icon/cr_iconset.ts
index 48c962b..271d9395 100644
--- a/ui/webui/resources/cr_elements/cr_icon/cr_iconset.ts
+++ b/ui/webui/resources/cr_elements/cr_icon/cr_iconset.ts
@@ -123,6 +123,10 @@
     if (contentViewBox) {
       svgClone.setAttribute('viewBox', contentViewBox);
     }
+    svgClone.style.display = 'block';
+    svgClone.style.height = '100%';
+    svgClone.style.width = '100%';
+    svgClone.style.pointerEvents = 'none';
     svgClone.appendChild(content);
     return svgClone;
   }
diff --git a/ui/webui/resources/tools/bundle_js.py b/ui/webui/resources/tools/bundle_js.py
index a935a1d..68841782 100755
--- a/ui/webui/resources/tools/bundle_js.py
+++ b/ui/webui/resources/tools/bundle_js.py
@@ -174,6 +174,8 @@
       '[name].rollup.js',
       '--sourcemap',
       '--sourcemapExcludeSources',
+      '--importAttributesKey',
+      'with',
       '--config',
       rollup_config_file,
   ])
diff --git a/ui/webui/resources/tools/bundle_js_test.py b/ui/webui/resources/tools/bundle_js_test.py
index 7adc745..744d068f 100755
--- a/ui/webui/resources/tools/bundle_js_test.py
+++ b/ui/webui/resources/tools/bundle_js_test.py
@@ -241,6 +241,20 @@
     depfile_d = self._read_out_file('depfile.d')
     self._check_dep_file(['src/bar.js'], depfile_d)
 
+  def testSimpleBundleExcludesCss(self):
+    args = [
+        '--host',
+        'fake-host',
+        '--js_module_in_files',
+        'foo_with_css.js',
+    ]
+    self._run_bundle(args)
+
+    self._check_bundle_output('foo_with_css_excludes_css.rollup.js',
+                              'foo_with_css.rollup.js')
+    depfile_d = self._read_out_file('depfile.d')
+    self._check_dep_file(['src/foo.js'], depfile_d)
+    self.assertNotIn('src/foo.css', depfile_d)
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/ui/webui/resources/tools/rollup_plugin.mjs b/ui/webui/resources/tools/rollup_plugin.mjs
index 1362a0a8..169a0d1e 100644
--- a/ui/webui/resources/tools/rollup_plugin.mjs
+++ b/ui/webui/resources/tools/rollup_plugin.mjs
@@ -40,12 +40,13 @@
  * @param {string} urlSrcPath The path that corresponds to the URL prefix.
  * @param {!Array<string>} excludes List of paths that should be excluded from
  *     bundling.
+ * @param {boolean} isCss Whether the file is a CSS file.
  * @return {string} The path to |source|. If |source| does not map to
  *     |urlSrcPath|, returns an empty string. If |source| maps to a location
  *     in |urlSrcPath| but is listed in |excludes|, returns the URL
  *     corresponding to |source|. Otherwise, returns the full path for |source|.
  */
-function getPathForUrl(source, origin, urlPrefix, urlSrcPath, excludes) {
+function getPathForUrl(source, origin, urlPrefix, urlSrcPath, excludes, isCss) {
   if (source === urlPrefix) {
     // Handle case where 'urlPrefix` matches the entire `source` URL and
     // therefore is not just a prefix, but a complete URL.
@@ -74,7 +75,8 @@
     return '';
   }
 
-  if (excludes.includes(urlPrefix + pathFromUrl) ||
+  if (isCss ||
+      excludes.includes(urlPrefix + pathFromUrl) ||
       excludes.includes(schemeRelativeUrl + pathFromUrl)) {
     return urlPrefix + pathFromUrl;
   }
@@ -116,13 +118,16 @@
             `Invalid path (missing file extension) was found: ${source}`);
       }
 
+      const isCss = path.extname(source) === '.css';
+
       // Normalize origin paths to use forward slashes.
       if (origin) {
         origin = normalizeSlashes(origin);
       }
 
       for (const [url, path] of urlsToPaths) {
-        const resultPath = getPathForUrl(source, origin, url, path, excludes);
+        const resultPath =
+            getPathForUrl(source, origin, url, path, excludes, isCss);
         if (resultPath.includes('://') || resultPath.startsWith('//')) {
           return {id: resultPath, external: 'absolute'};
         } else if (resultPath) {
@@ -132,7 +137,7 @@
 
       // Check if the URL is an external path that isn't mapped.
       if (source.includes('://') || source.startsWith('//')) {
-        if (excludes.includes(source)) {
+        if (isCss || excludes.includes(source)) {
           return {id: source, external: 'absolute'};
         } else {
           this.error(`Invalid absolute path: ${source} is not in |excludes| ` +
@@ -149,7 +154,7 @@
           combinePaths(origin, source);
       if (fullSourcePath.startsWith(rootPath)) {
         const pathFromRoot = relativePath(rootPath, fullSourcePath);
-        if (excludes.includes(pathFromRoot)) {
+        if (isCss || excludes.includes(pathFromRoot)) {
           const url = new URL(pathFromRoot, hostUrl);
           return source.startsWith('/') ?
               {id: url.href, external: 'absolute'} :
diff --git a/ui/webui/resources/tools/tests/bundle_js/expected/foo_with_css_excludes_css.rollup.js b/ui/webui/resources/tools/tests/bundle_js/expected/foo_with_css_excludes_css.rollup.js
new file mode 100644
index 0000000..2e891cf
--- /dev/null
+++ b/ui/webui/resources/tools/tests/bundle_js/expected/foo_with_css_excludes_css.rollup.js
@@ -0,0 +1,40 @@
+import sheet from './foo.css' with { type: 'css' };
+
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+alert('Hello from resources/foo_resource.js');
+
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+alert('Hello from external/bar/bar.js');
+
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+alert('Hello from external/foo/foo.js');
+
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+alert('Hello from external/baz/baz.js');
+
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+alert('Hello from src/foo.js');
+
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+document.getElementById('foo').shadowRoot.adoptedStyleSheets = [sheet];
+//# sourceMappingURL=foo_with_css.rollup.js.map
diff --git a/ui/webui/resources/tools/tests/bundle_js/src/foo_with_css.js b/ui/webui/resources/tools/tests/bundle_js/src/foo_with_css.js
new file mode 100644
index 0000000..19edfbb6
--- /dev/null
+++ b/ui/webui/resources/tools/tests/bundle_js/src/foo_with_css.js
@@ -0,0 +1,9 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import './foo.js';
+
+import sheet from './foo.css' with {type : 'css'};
+
+document.getElementById('foo').shadowRoot.adoptedStyleSheets = [sheet];
diff --git a/ui/webui/webui_util.cc b/ui/webui/webui_util.cc
index 9b9876ea..1cc3ea3 100644
--- a/ui/webui/webui_util.cc
+++ b/ui/webui/webui_util.cc
@@ -65,8 +65,10 @@
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::FontSrc,
       base::StringPrintf("font-src %s://resources 'self';", scheme.c_str()));
-  // unsafe-inline is required for Polymer. Allow styles to be imported from
-  // //resources and //theme.
+  // unsafe-inline is required for Polymer and for CSS shims, which
+  // require <style> tags directly in the main .html file. Also
+  // required by some Lit elements that set style= in HTML.
+  // Allow styles to be imported from  //resources and //theme.
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::StyleSrc,
       base::StringPrintf(