diff --git a/DEPS b/DEPS
index c6ac25f..a9372b8 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': 'cba3ac5c7ab064bf435a44715d29dab39e052243',
+  'skia_revision': '156de4c06c2719e4d6bb3929a0dad88c250fc3d8',
   # 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': '306e58a9fb0b1d8500165d6bde1796061ab97ba6',
+  'angle_revision': '173b937deb470f2972e1af58206eae43813412e4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -332,7 +332,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
-  'boringssl_revision': 'cb6da2b637b89a289403a08f3c5d8d4f75b9227c',
+  'boringssl_revision': '3e8ad4eb8421e3c76530123a0a98b1cd175dae76',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
@@ -380,7 +380,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
-  'crossbench_revision': '3e62914554aedf28633147fdaa9ff1a2023bc544',
+  'crossbench_revision': '8a1fc2cc14665e17a3fae3452da9efad18238ed8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # 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': 'f15d982497b84034c15b4b33d039a84cdef53ccf',
+  'devtools_frontend_revision': '10522a4c85f86d03336d2e4fb3b75fd49a35bd46',
   # 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.
@@ -532,7 +532,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling llvm-libc
   # and whatever else without interference from each other.
-  'compiler_rt_revision': '90d04a8f70c0d92a9a436fc9706c4db5675d7dd3',
+  'compiler_rt_revision': '4ff2d7fc52769a0e233abcf1298cb090b86b59a2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling clusterfuzz-data
   # and whatever else without interference from each other.
@@ -1195,7 +1195,7 @@
       'packages': [
           {
               'package': 'chromium/chrome/android/orderfiles/arm',
-              'version': 'qYjQMzS7DhD_BvNzsPE_DT18ak0929FbtH1WOI4wYx0C',
+              'version': 'NyGKwqUul1XW__6e3nPiK56_rRoPxLz1sNkZkbN8gM8C',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -1206,7 +1206,7 @@
       'packages': [
           {
               'package': 'chromium/chrome/android/orderfiles/arm64',
-              'version': 'pUoFcAnetS5Y_E80E9B4ieMEgCFHnuRxTmSiSe784lAC',
+              'version': 'fLKpowkTt5Bnw8eOQhv6t_nIyaDoxinFE2-NEO_Wd2cC',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -1217,7 +1217,7 @@
       'packages': [
           {
               'package': 'chromium/android_webview/tools/orderfiles/arm',
-              'version': 'bgNEQN1D-ti3EaFkwCiCpsbhOwZY1ypAxCj6dgyCQ70C',
+              'version': 'ZKWKc4FHYQzRfD9PM9RO2ScuBLl6AT6KmCptfNx93VkC',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -1613,7 +1613,7 @@
     'packages': [
       {
         'package': 'chromium/chrome/test/data/variations/cipd',
-        'version': 'h6IhLpPJXuzQaYKXYjzqgp2xkbAO2gg-gHV7qP9-ipEC',
+        'version': 'VZlrSA6HejroTid9molsauhHqlIF0OIkeQRqDC4yoooC',
       },
     ],
     'dep_type': 'cipd',
@@ -1624,7 +1624,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '707d85242b4fbacce36e4c6de218dfd44d9a9708',
+    '8ef6ed3b6b8acee05b6b2f324002eedf21035d63',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -2929,16 +2929,16 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@bc49dedd8bfece8b76a44145a432817f2ea259b5',
-  'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@6cfcfaf1985a765c1691f12d413a2fd2945e918f',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@0803a732819f5ee1550d0dd014060560cdf87ce1',
+  'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@aea55ab8e770e1403374e5cea2266b3223300f71',
   '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@b824a462d4256d720bebb40e78b9eb8f78bbb305',
-  'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@186078866b88db9a91ef985b98f666c29241a869',
+  'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@971a7b6e8d7740035bbff089bbbf9f42951ecfd5',
   'src/third_party/vulkan-headers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Headers@65586d13fb197279942581ba9c2eb2c6b664487c',
-  'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@65e5428032e4ccb2e064fa8691b96e61701d5956',
+  'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@5f6d4be882cb015228c994f81098220dada056b7',
   'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@2a3347d5e74d359e3ecb8e229917f3335bfa2dfa',
   'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@9aa2c08f82e3fb18d43e37e44015a79af7f3b672',
-  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@c1054dbc91e22cb6d99bf5b0766c4efb18dc2bd6',
+  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@e114bc2b59dadb4c07eb8cff7873c6a4b366543b',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'cb0597213b0fcb999caa9ed08c2f88dc45eb7d50',
@@ -2981,7 +2981,7 @@
     Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'),
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '89e8619bfdacfd1364bf3fc6bc5388c92629ab71',
+    Var('webrtc_git') + '/src.git' + '@' + 'ff73efd929c8b8362a28dfe6708bf16d0fa34c2b',
 
   # 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.
@@ -3125,7 +3125,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'aFzzGqpXziyLwTWrfqoybYuaqbYOzXzFAvjxtJovkeEC',
+        'version': 'lpCcjzmNG9EAwBqFnJWc2fhEQLsd7XmOeSjhnAiOA9cC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3136,7 +3136,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'UzYEjE8Ep_weWOBj6luEv075ZOS5xb31bO2AsNUsRHkC',
+        'version': 'gBJOnbnhVqR2NtW6_d0J3W3tT8xcvP0DvxoEhW5zCaEC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3691,7 +3691,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        '724de88d56f431a79762af82b5efec95e6601191',
+        'd0ff238e3fc92066db0aebb246f7203918a57de5',
       'condition': 'checkout_src_internal',
   },
 
diff --git a/agents/prompts/eval/class_refactor/eval.promptfoo.yaml b/agents/prompts/eval/class_refactor/eval.promptfoo.yaml
new file mode 100644
index 0000000..33303f3b
--- /dev/null
+++ b/agents/prompts/eval/class_refactor/eval.promptfoo.yaml
@@ -0,0 +1,64 @@
+description: |
+  Owner: dmurph@
+  Description: Refactor a class in prompt.md
+prompts:
+  - 'file://prompt.md'
+providers:
+  - id: python:../../../testing/gemini_provider.py
+    config:
+      timeoutSeconds: 3000
+      changes:
+        - apply: agents/prompts/eval/class_refactor/fake_class.patch
+tests:
+  - assert:
+    - type: python
+      value: file://../../../testing/asserts/check_changes.py:check_files_added
+      config:
+        files:
+        - base/strings/nested_struct.h
+        - base/strings/nested_struct.cc
+    - type: python
+      value: file://../../../testing/asserts/check_changes.py:check_files_changed
+      config:
+        files:
+        - base/BUILD.gn
+    - type: python
+      value: file://../../../testing/asserts/check_changes.py:check_file_content
+      config:
+        files:
+          - path: 'base/strings/fake_simple_class.h'
+            absent:
+              - 'struct NestedStruct'
+          - path: 'base/strings/fake_simple_class.cc'
+            absent:
+              - 'CreateNestedStruct'
+            present:
+              - 'NestedClass'
+          - path: 'base/strings/nested_struct.h'
+            present:
+              - 'NestedClass'
+          - path: 'base/BUILD.gn'
+            present:
+              - '"strings/nested_struct.cc",'
+              - '"strings/nested_struct.h",'
+    - type: python
+      value: file://../../../testing/asserts/check_changes.py:check_files_exist
+      config:
+        files:
+        - out/Default/obj/base/base/nested_struct.o
+    - type: model-graded-closedqa
+      value: |
+        The user was asked to refactor the struct `NestedStruct` from the class `FakeSimpleClass` into its own class `NestedClass`.
+
+        Verify that the following changes were made:
+        1. New files `base/strings/nested_struct.h` and `base/strings/nested_struct.cc` were created.
+        2. The new class `NestedClass` is defined and implemented in the new files.
+        3. `FakeSimpleClass` in `base/strings/fake_simple_class.h` and `base/strings/fake_simple_class.cc` is updated to use `NestedClass`.
+        4. The struct `NestedStruct` is removed from `base/strings/fake_simple_class.h`.
+        5. `base/BUILD.gn` is updated to include `strings/nested_struct.cc` and `strings/nested_struct.h` and build successfully.
+
+        Were all these changes correctly applied in the response?
+      provider: python:agents/testing/gemini_provider.py
+    metadata:
+      precompile_targets:
+      - base_unittests
diff --git a/agents/prompts/eval/class_refactor/fake_class.patch b/agents/prompts/eval/class_refactor/fake_class.patch
new file mode 100644
index 0000000..197bab40
--- /dev/null
+++ b/agents/prompts/eval/class_refactor/fake_class.patch
@@ -0,0 +1,85 @@
+
+diff --git a/base/BUILD.gn b/base/BUILD.gn
+index 0573c2db7639..5601a403c482 100644
+--- a/base/BUILD.gn
++++ b/base/BUILD.gn
+@@ -624,6 +624,8 @@ component("base") {
+     "strings/cstring_view.h",
+     "strings/durable_string_view.h",
+     "strings/escape.cc",
+     "strings/escape.h",
++    "strings/fake_simple_class.cc",
++    "strings/fake_simple_class.h",
+     "strings/latin1_string_conversions.cc",
+     "strings/latin1_string_conversions.h",
+     "strings/lazy_string_builder.cc",
+diff --git a/base/strings/fake_simple_class.cc b/base/strings/fake_simple_class.cc
+new file mode 100644
+index 0000000000000..d811801c431d1
+--- /dev/null
++++ b/base/strings/fake_simple_class.cc
+@@ -0,0 +1,18 @@
++// Copyright 2025 The Chromium Authors
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "base/strings/fake_simple_class.h"
++
++#include "base/logging.h"
++
++namespace base {
++
++FakeSimpleClass::FakeSimpleClass() = default;
++FakeSimpleClass::~FakeSimpleClass() = default;
++
++void FakeSimpleClass::ProcessStruct(const NestedStruct& s) {
++  if (s.field1) {
++    LOG(INFO) << "Field2: " << s.field2;
++  }
++}
++
++// static
++FakeSimpleClass::NestedStruct FakeSimpleClass::CreateNestedStruct(
++    bool field1,
++    const std::string& field2) {
++  NestedStruct s;
++  s.field1 = field1;
++  s.field2 = field2;
++  return s;
++}
++
++}  // namespace base
+diff --git a/base/strings/fake_simple_class.h b/base/strings/fake_simple_class.h
+new file mode 100644
+index 0000000000000..8b3a3c1a2d1f4
+--- /dev/null
++++ b/base/strings/fake_simple_class.h
+@@ -0,0 +1,22 @@
++// Copyright 2025 The Chromium Authors
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef BASE_STRINGS_FAKE_SIMPLE_CLASS_H_
++#define BASE_STRINGS_FAKE_SIMPLE_CLASS_H_
++
++#include <string>
++
++namespace base {
++
++class FakeSimpleClass {
++ public:
++  struct NestedStruct {
++    bool field1 = false;
++    std::string field2;
++  };
++
++  FakeSimpleClass();
++  ~FakeSimpleClass();
++
++  void ProcessStruct(const NestedStruct& s);
++  static NestedStruct CreateNestedStruct(bool field1, const std::string& field2);
++};
++
++}  // namespace base
++
++#endif  // BASE_STRINGS_FAKE_SIMPLE_CLASS_H_
diff --git a/agents/prompts/eval/class_refactor/prompt.md b/agents/prompts/eval/class_refactor/prompt.md
new file mode 100644
index 0000000..d31386f9
--- /dev/null
+++ b/agents/prompts/eval/class_refactor/prompt.md
@@ -0,0 +1,14 @@
+Could you please refactor the `NestedStruct` from the `FakeSimpleClass`?
+I'd like it to be its own class called `NestedClass`.
+
+Please create new files `base/strings/nested_struct.h` and
+`base/strings/nested_struct.cc` for the new `NestedClass`.
+
+In the new `NestedClass`, please make the member variables private and create
+public getter and setter methods for them. Also, please add a constructor to
+initialize the members.
+
+Next, please update `FakeSimpleClass` to use this new `NestedClass`.
+
+Finally, please update the build files and compile the code to ensure that the
+refactoring was successful.
diff --git a/agents/testing/asserts/check_changes.py b/agents/testing/asserts/check_changes.py
index 54154d35..c012b10 100644
--- a/agents/testing/asserts/check_changes.py
+++ b/agents/testing/asserts/check_changes.py
@@ -80,3 +80,45 @@
             'score': 0
         }
     return {'pass': True, 'reason': 'All expected files exist.', 'score': 1}
+
+
+def check_file_content(_: str, context):
+    """Checks if files contain or do not contain specific strings."""
+    file_configs = context.get('config', {}).get('files', [])
+    errors = []
+
+    for config in file_configs:
+        file_path = config.get('path')
+        if not file_path:
+            continue
+
+        try:
+            with open(file_path, 'r', encoding='utf-8') as f:
+                content = f.read()
+        except FileNotFoundError:
+            errors.append(f'File not found: {file_path}')
+            continue
+        except Exception as e:
+            errors.append(f'Error reading file {file_path}: {e}')
+            continue
+
+        for s in config.get('present', []):
+            if s not in content:
+                errors.append(
+                    f'Expected to find "{s}" in {file_path}, but it was not '
+                    'found.')
+
+        for s in config.get('absent', []):
+            if s in content:
+                errors.append(
+                    f'Expected to not find "{s}" in {file_path}, but it was '
+                    'found.')
+
+    if errors:
+        return {'pass': False, 'reason': '\n'.join(errors), 'score': 0}
+
+    return {
+        'pass': True,
+        'reason': 'All file content checks passed.',
+        'score': 1
+    }
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
index 07768ef7..4e34f3b 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -284,6 +284,9 @@
                 AutofillFeatures.AUTOFILL_POLICY_CONTROLLED_FEATURE_AUTOFILL,
                 "Enables the policy-controlled feature \"autofill\"."),
         Flag.baseFeature(
+                AutofillFeatures.AUTOFILL_POLICY_CONTROLLED_FEATURE_MANUAL_TEXT,
+                "Enables the policy-controlled feature \"manual-text\"."),
+        Flag.baseFeature(
                 AutofillFeatures.AUTOFILL_PAGE_LANGUAGE_DETECTION,
                 "Enables Autofill to retrieve the page language for form parsing."),
         Flag.baseFeature(
@@ -1067,6 +1070,10 @@
                 CcFeatures.OVERSCROLL_BEHAVIOR_RESPECTED_ON_ALL_SCROLL_CONTAINERS,
                 "Enables overscroll-behavior to be respected on all scroll containers."),
         Flag.baseFeature(
+                CcFeatures.OVERSCROLL_EFFECT_ON_NON_ROOT_SCROLLERS,
+                "Enables elastic overscroll effect on scrollers other than the root "
+                        + "document (e.g. iframes and overflow areas)."),
+        Flag.baseFeature(
                 BlinkFeatures.SEPARATE_DEFER_MODULE_SCRIPT_TASKS,
                 "Enables yielding to the event loop between executing deferred module scripts to"
                         + " improve responsiveness."),
diff --git a/android_webview/renderer/aw_content_renderer_client.cc b/android_webview/renderer/aw_content_renderer_client.cc
index b43a088..9a09c8c4 100644
--- a/android_webview/renderer/aw_content_renderer_client.cc
+++ b/android_webview/renderer/aw_content_renderer_client.cc
@@ -227,6 +227,10 @@
           autofill::features::kAutofillPolicyControlledFeatureAutofill)) {
     blink::WebRuntimeFeatures::EnableAutofill(true);
   }
+  if (base::FeatureList::IsEnabled(
+          autofill::features::kAutofillPolicyControlledFeatureManualText)) {
+    blink::WebRuntimeFeatures::EnableManualText(true);
+  }
 
   // Enable the overall android.webview namespace.
   blink::WebRuntimeFeatures::EnableBlinkExtensionWebView(true);
diff --git a/android_webview/test/data/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/android_webview/test/data/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 02d0c61..002f96af 100644
--- a/android_webview/test/data/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/android_webview/test/data/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -6019,6 +6019,7 @@
     getter webdriver
     getter webkitPersistentStorage
     getter webkitTemporaryStorage
+    getter windowControlsOverlay
     method clearAppBadge
     method constructor
     method getBattery
@@ -10760,6 +10761,18 @@
     attribute PERSISTENT
     attribute TEMPORARY
     method constructor
+interface WindowControlsOverlay : EventTarget
+    attribute @@toStringTag
+    getter ongeometrychange
+    getter visible
+    method constructor
+    method getTitlebarAreaRect
+    setter ongeometrychange
+interface WindowControlsOverlayGeometryChangeEvent : Event
+    attribute @@toStringTag
+    getter titlebarAreaRect
+    getter visible
+    method constructor
 interface Worker : EventTarget
     attribute @@toStringTag
     getter onerror
diff --git a/ash/accessibility/chromevox/touch_exploration_controller_unittest.cc b/ash/accessibility/chromevox/touch_exploration_controller_unittest.cc
index 2b517725..37661d0 100644
--- a/ash/accessibility/chromevox/touch_exploration_controller_unittest.cc
+++ b/ash/accessibility/chromevox/touch_exploration_controller_unittest.cc
@@ -1165,8 +1165,8 @@
         distance / gesture_detector_config_.maximum_fling_velocity;
     // delta_time is in seconds, so we convert to ms.
     int delta_time_ms = floor(delta_time * 1000);
-    generator_->GestureMultiFingerScroll(num_fingers, start_points,
-                                         delta_time_ms, kSteps, move_x, move_y);
+    generator_->GestureMultiFingerScroll(start_points, delta_time_ms, kSteps,
+                                         move_x, move_y);
     EXPECT_EQ(expected_gesture, delegate_.GetLastGesture());
     EXPECT_TRUE(IsInNoFingersDownState());
     EXPECT_FALSE(IsInTouchToMouseMode());
@@ -1226,8 +1226,8 @@
         distance / gesture_detector_config_.maximum_fling_velocity;
     // delta_time is in seconds, so we convert to ms.
     int delta_time_ms = floor(delta_time * 1000);
-    generator_->GestureMultiFingerScroll(num_fingers, start_points,
-                                         delta_time_ms, kSteps, move_x, move_y);
+    generator_->GestureMultiFingerScroll(start_points, delta_time_ms, kSteps,
+                                         move_x, move_y);
     EXPECT_EQ(expected_gesture, delegate_.GetLastGesture());
     EXPECT_TRUE(IsInNoFingersDownState());
     EXPECT_FALSE(IsInTouchToMouseMode());
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 6e7bbf0..e40b0190 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -225,7 +225,7 @@
 BASE_FEATURE(kBocaNavSettingsDialog, base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables the new caption toggle button for boca.
-BASE_FEATURE(kBocaCaptionToggle, base::FEATURE_ENABLED_BY_DEFAULT);
+BASE_FEATURE(kBocaCaptionToggle, base::FEATURE_DISABLED_BY_DEFAULT);
 
 // Enables or disables using the native ChromeOS implementation of the CRD
 // client for Spotlight within the Boca SWA.
diff --git a/ash/webui/boca_receiver_app_ui/boca_receiver_untrusted_page_handler.cc b/ash/webui/boca_receiver_app_ui/boca_receiver_untrusted_page_handler.cc
index e6f20f54..cbd47b0 100644
--- a/ash/webui/boca_receiver_app_ui/boca_receiver_untrusted_page_handler.cc
+++ b/ash/webui/boca_receiver_app_ui/boca_receiver_untrusted_page_handler.cc
@@ -314,8 +314,10 @@
       connection_info_->connection_details().initiator().user_identity();
   const ::boca::UserIdentity& presenter =
       connection_info_->connection_details().presenter().user_identity();
+  const bool is_initiator_presenting =
+      initiator.gaia_id() == presenter.gaia_id();
   page_->OnConnecting(mojom::UserInfo::New(initiator.full_name()),
-                      initiator.gaia_id() != presenter.gaia_id()
+                      !is_initiator_presenting
                           ? mojom::UserInfo::New(presenter.full_name())
                           : nullptr);
   connection_info_->set_receiver_connection_state(
@@ -337,6 +339,15 @@
       base::BindRepeating(
           &BocaReceiverUntrustedPageHandler::OnCrdConnectionStateUpdated,
           weak_ptr_factory_.GetWeakPtr()));
+  if (!is_initiator_presenting) {
+    connection_info_poller_.Start(
+        receiver_id_.value(), connection_info_->connection_id(),
+        delegate_->CreateRequestSender(
+            kRequesterId, GetReceiverConnectionInfoRequest::kTrafficAnnotation),
+        base::BindOnce(
+            &BocaReceiverUntrustedPageHandler::OnConnectionClosedByPoller,
+            weak_ptr_factory_.GetWeakPtr()));
+  }
 }
 
 void BocaReceiverUntrustedPageHandler::MaybeEndConnection(
@@ -356,6 +367,7 @@
   auto connection_state = reason == mojom::ConnectionClosedReason::kError
                               ? ::boca::ReceiverConnectionState::ERROR
                               : ::boca::ReceiverConnectionState::DISCONNECTED;
+  connection_info_poller_.Stop();
   UpdateConnection(connection_info_->connection_id(), connection_state);
   connection_info_.reset();
 }
@@ -437,6 +449,13 @@
   Init();
 }
 
+void BocaReceiverUntrustedPageHandler::OnConnectionClosedByPoller(
+    bool server_unreachable) {
+  MaybeEndConnection(server_unreachable
+                         ? mojom::ConnectionClosedReason::kError
+                         : mojom::ConnectionClosedReason::kInitiatorClosed);
+}
+
 boca::FCMHandler* BocaReceiverUntrustedPageHandler::fcm_handler() const {
   return delegate_->GetFcmHandler();
 }
diff --git a/ash/webui/boca_receiver_app_ui/boca_receiver_untrusted_page_handler.h b/ash/webui/boca_receiver_app_ui/boca_receiver_untrusted_page_handler.h
index d945953..d33468a 100644
--- a/ash/webui/boca_receiver_app_ui/boca_receiver_untrusted_page_handler.h
+++ b/ash/webui/boca_receiver_app_ui/boca_receiver_untrusted_page_handler.h
@@ -17,6 +17,7 @@
 #include "chromeos/ash/components/boca/boca_request.h"
 #include "chromeos/ash/components/boca/invalidations/fcm_handler.h"
 #include "chromeos/ash/components/boca/proto/receiver.pb.h"
+#include "chromeos/ash/components/boca/receiver/receiver_connection_info_poller.h"
 #include "chromeos/ash/components/boca/retriable_request_sender.h"
 #include "chromeos/services/network_config/public/cpp/cros_network_config_observer.h"
 #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
@@ -110,6 +111,8 @@
       std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
           networks) override;
 
+  void OnConnectionClosedByPoller(bool server_unreachable);
+
   boca::FCMHandler* fcm_handler() const;
 
   boca::SpotlightRemotingClientManager* remoting_client() const;
@@ -124,6 +127,7 @@
   std::optional<::boca::KioskReceiverConnection> connection_info_;
   std::unique_ptr<UpdateReceiverStateRequestSender>
       update_connection_retriable_sender_;
+  ReceiverConnectionInfoPoller connection_info_poller_;
 
   mojo::Remote<chromeos::network_config::mojom::CrosNetworkConfig>
       cros_network_config_;
diff --git a/ash/webui/boca_receiver_app_ui/boca_receiver_untrusted_page_handler_unittest.cc b/ash/webui/boca_receiver_app_ui/boca_receiver_untrusted_page_handler_unittest.cc
index c8433ee..fb768ed3 100644
--- a/ash/webui/boca_receiver_app_ui/boca_receiver_untrusted_page_handler_unittest.cc
+++ b/ash/webui/boca_receiver_app_ui/boca_receiver_untrusted_page_handler_unittest.cc
@@ -22,6 +22,7 @@
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
+#include "base/time/time.h"
 #include "chromeos/ash/components/boca/invalidations/fcm_handler.h"
 #include "chromeos/ash/components/boca/invalidations/invalidation_service_delegate.h"
 #include "chromeos/ash/components/boca/invalidations/invalidation_service_impl.h"
@@ -37,6 +38,7 @@
 #include "google_apis/common/request_sender.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "net/http/http_status_code.h"
 #include "remoting/proto/audio.pb.h"
 #include "services/network/public/cpp/resource_request_body.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -292,6 +294,14 @@
         base::Value(shill::kStateOnline));
   }
 
+  std::string GetConnectionInfoUrlWithConnectionId() {
+    return base::StrCat(
+        {get_connection_url_.spec(), "?",
+         base::ReplaceStringPlaceholders(
+             GetReceiverConnectionInfoRequest::kConnectionIdQueryParam,
+             {std::string(kConnectionId)}, /*offsets=*/nullptr)});
+  }
+
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   network_config::CrosNetworkConfigTestHelper cros_network_config_helper_;
@@ -426,6 +436,11 @@
   EXPECT_EQ(initiator->name, kInitiatorName);
   ASSERT_FALSE(presenter.is_null());
   EXPECT_EQ(presenter->name, kPresenterName);
+
+  // Polling should start since the initiator is not the presenter.
+  task_environment_.FastForwardBy(base::Seconds(10));
+  EXPECT_TRUE(
+      url_loader_factory_.IsPending(GetConnectionInfoUrlWithConnectionId()));
 }
 
 TEST_F(BocaReceiverUntrustedPageHandlerTest,
@@ -452,6 +467,11 @@
   ASSERT_FALSE(initiator.is_null());
   EXPECT_EQ(initiator->name, kInitiatorName);
   EXPECT_TRUE(presenter.is_null());
+
+  // Polling should not start since the initiator is the presenter.
+  task_environment_.FastForwardBy(base::Seconds(10));
+  EXPECT_FALSE(
+      url_loader_factory_.IsPending(GetConnectionInfoUrlWithConnectionId()));
 }
 
 TEST_F(BocaReceiverUntrustedPageHandlerTest, FrameReceived) {
@@ -731,6 +751,85 @@
   EXPECT_EQ(connection_closed_future.Get(),
             mojom::ConnectionClosedReason::kInitiatorClosed);
   EXPECT_EQ(GetRequestBody(update_connection_url_), kDisconnectedPair);
+
+  // No more polling after stop.
+  task_environment_.FastForwardBy(base::Seconds(10));
+  EXPECT_FALSE(
+      url_loader_factory_.IsPending(GetConnectionInfoUrlWithConnectionId()));
+}
+
+TEST_F(BocaReceiverUntrustedPageHandlerTest, StopRequestedFetchedByPolling) {
+  base::test::TestFuture<mojom::ConnectionClosedReason>
+      connection_closed_future;
+  // Establish a connection first.
+  url_loader_factory_.AddResponse(get_connection_url_.spec(),
+                                  CreateConnectionInfo(kConnectionId));
+  EXPECT_CALL(*remoting_client_, StartCrdClient).Times(1);
+  handler_ = std::make_unique<BocaReceiverUntrustedPageHandler>(
+      page_.BindAndGetRemote(), &handler_delegate_);
+  // Wait for CONNECTING update.
+  url_loader_factory_.WaitForRequest(update_connection_url_);
+  url_loader_factory_.SimulateResponseForPendingRequest(
+      update_connection_url_.spec(), kConnectingPair);
+
+  // Now simulate a STOP_REQUESTED response for polling.
+  url_loader_factory_.AddResponse(
+      GetConnectionInfoUrlWithConnectionId(),
+      CreateConnectionInfo(kConnectionId, "STOP_REQUESTED"));
+  EXPECT_CALL(page_, OnConnectionClosed)
+      .WillOnce(
+          [&connection_closed_future](mojom::ConnectionClosedReason reason) {
+            connection_closed_future.GetCallback().Run(reason);
+          });
+  EXPECT_CALL(*remoting_client_, StopCrdClient).Times(1);
+
+  task_environment_.FastForwardBy(base::Seconds(10));
+
+  EXPECT_EQ(connection_closed_future.Get(),
+            mojom::ConnectionClosedReason::kInitiatorClosed);
+  EXPECT_EQ(GetRequestBody(update_connection_url_), kDisconnectedPair);
+
+  // No more polling after stop.
+  task_environment_.FastForwardBy(base::Seconds(10));
+  EXPECT_FALSE(
+      url_loader_factory_.IsPending(GetConnectionInfoUrlWithConnectionId()));
+}
+
+TEST_F(BocaReceiverUntrustedPageHandlerTest, StopByPollingFailure) {
+  base::test::TestFuture<mojom::ConnectionClosedReason>
+      connection_closed_future;
+  // Establish a connection first.
+  url_loader_factory_.AddResponse(get_connection_url_.spec(),
+                                  CreateConnectionInfo(kConnectionId));
+  EXPECT_CALL(*remoting_client_, StartCrdClient).Times(1);
+  handler_ = std::make_unique<BocaReceiverUntrustedPageHandler>(
+      page_.BindAndGetRemote(), &handler_delegate_);
+  // Wait for CONNECTING update.
+  url_loader_factory_.WaitForRequest(update_connection_url_);
+  url_loader_factory_.SimulateResponseForPendingRequest(
+      update_connection_url_.spec(), kConnectingPair);
+
+  // Simulate polling failure.
+  url_loader_factory_.AddResponse(GetConnectionInfoUrlWithConnectionId(),
+                                  /*content=*/"",
+                                  net::HTTP_SERVICE_UNAVAILABLE);
+  EXPECT_CALL(page_, OnConnectionClosed)
+      .WillOnce(
+          [&connection_closed_future](mojom::ConnectionClosedReason reason) {
+            connection_closed_future.GetCallback().Run(reason);
+          });
+  EXPECT_CALL(*remoting_client_, StopCrdClient).Times(1);
+
+  task_environment_.FastForwardBy(base::Seconds(30));
+
+  EXPECT_EQ(connection_closed_future.Get(),
+            mojom::ConnectionClosedReason::kError);
+  EXPECT_EQ(GetRequestBody(update_connection_url_), kErrorPair);
+
+  // No more polling after stop.
+  task_environment_.FastForwardBy(base::Seconds(10));
+  EXPECT_FALSE(
+      url_loader_factory_.IsPending(GetConnectionInfoUrlWithConnectionId()));
 }
 
 TEST_F(BocaReceiverUntrustedPageHandlerTest, StopRequestedDifferentConnection) {
diff --git a/ash/wm/system_gesture_event_filter_unittest.cc b/ash/wm/system_gesture_event_filter_unittest.cc
index cab6238..7cb08dd 100644
--- a/ash/wm/system_gesture_event_filter_unittest.cc
+++ b/ash/wm/system_gesture_event_filter_unittest.cc
@@ -105,16 +105,14 @@
 
   WindowState* toplevel_state = WindowState::Get(toplevel->GetNativeWindow());
   // Swipe down to minimize.
-  generator.GestureMultiFingerScroll(kTouchPoints, kInitialPoints, 15, kSteps,
-                                     0, 150);
+  generator.GestureMultiFingerScroll(kInitialPoints, 15, kSteps, 0, 150);
   EXPECT_TRUE(toplevel_state->IsMinimized());
 
   toplevel->Restore();
   toplevel->GetNativeWindow()->SetBounds(bounds);
 
   // Swipe up to maximize.
-  generator.GestureMultiFingerScroll(kTouchPoints, kInitialPoints, 15, kSteps,
-                                     0, -150);
+  generator.GestureMultiFingerScroll(kInitialPoints, 15, kSteps, 0, -150);
   EXPECT_TRUE(toplevel_state->IsMaximized());
 
   toplevel->Restore();
@@ -122,8 +120,7 @@
 
   // Swipe right to snap.
   gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
-  generator.GestureMultiFingerScroll(kTouchPoints, kInitialPoints, 15, kSteps,
-                                     150, 0);
+  generator.GestureMultiFingerScroll(kInitialPoints, 15, kSteps, 150, 0);
   gfx::Rect right_tile_bounds = toplevel->GetWindowBoundsInScreen();
   EXPECT_NE(normal_bounds.ToString(), right_tile_bounds.ToString());
 
@@ -132,15 +129,13 @@
   for (gfx::Point& point : left_points) {
     point.Offset(right_tile_bounds.x(), right_tile_bounds.y());
   }
-  generator.GestureMultiFingerScroll(kTouchPoints, left_points, 15, kSteps,
-                                     -150, 0);
+  generator.GestureMultiFingerScroll(left_points, 15, kSteps, -150, 0);
   gfx::Rect left_tile_bounds = toplevel->GetWindowBoundsInScreen();
   EXPECT_NE(normal_bounds.ToString(), left_tile_bounds.ToString());
   EXPECT_NE(right_tile_bounds.ToString(), left_tile_bounds.ToString());
 
   // Swipe right again.
-  generator.GestureMultiFingerScroll(kTouchPoints, kInitialPoints, 15, kSteps,
-                                     150, 0);
+  generator.GestureMultiFingerScroll(kInitialPoints, 15, kSteps, 150, 0);
   gfx::Rect current_bounds = toplevel->GetWindowBoundsInScreen();
   EXPECT_NE(current_bounds.ToString(), left_tile_bounds.ToString());
   EXPECT_EQ(current_bounds.ToString(), right_tile_bounds.ToString());
@@ -170,7 +165,7 @@
   ui::test::EventGenerator generator(root_window, toplevel->GetNativeWindow());
 
   // Swipe down to minimize.
-  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
+  generator.GestureMultiFingerScroll(points, 15, kSteps, 0, 150);
   WindowState* toplevel_state = WindowState::Get(toplevel->GetNativeWindow());
   EXPECT_TRUE(toplevel_state->IsMinimized());
 
@@ -178,7 +173,7 @@
   toplevel->GetNativeWindow()->SetBounds(bounds);
 
   // Check that swiping up doesn't maximize.
-  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, -150);
+  generator.GestureMultiFingerScroll(points, 15, kSteps, 0, -150);
   EXPECT_FALSE(toplevel_state->IsMaximized());
 
   toplevel->Restore();
@@ -186,7 +181,7 @@
 
   // Check that swiping right doesn't snap.
   gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
-  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
+  generator.GestureMultiFingerScroll(points, 15, kSteps, 150, 0);
   normal_bounds.set_x(normal_bounds.x() + 150);
   EXPECT_EQ(normal_bounds.ToString(),
             toplevel->GetWindowBoundsInScreen().ToString());
@@ -195,7 +190,7 @@
 
   // Check that swiping left doesn't snap.
   normal_bounds = toplevel->GetWindowBoundsInScreen();
-  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, -150, 0);
+  generator.GestureMultiFingerScroll(points, 15, kSteps, -150, 0);
   normal_bounds.set_x(normal_bounds.x() - 150);
   EXPECT_EQ(normal_bounds.ToString(),
             toplevel->GetWindowBoundsInScreen().ToString());
@@ -205,7 +200,7 @@
   // Swipe right again, make sure the window still doesn't snap.
   normal_bounds = toplevel->GetWindowBoundsInScreen();
   normal_bounds.set_x(normal_bounds.x() + 150);
-  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
+  generator.GestureMultiFingerScroll(points, 15, kSteps, 150, 0);
   EXPECT_EQ(normal_bounds.ToString(),
             toplevel->GetWindowBoundsInScreen().ToString());
 }
@@ -231,8 +226,8 @@
   EXPECT_EQ(HTLEFT, toplevel->GetNonClientComponent(points[0]));
   EXPECT_EQ(HTRIGHT, toplevel->GetNonClientComponent(points[1]));
 
-  GetEventGenerator()->GestureMultiFingerScrollWithDelays(
-      kTouchPoints, points, delays, 15, kSteps, 0, 40);
+  GetEventGenerator()->GestureMultiFingerScrollWithDelays(points, delays, 15,
+                                                          kSteps, 0, 40);
 
   // The window bounds should not have changed because neither of the fingers
   // moved horizontally.
@@ -269,8 +264,8 @@
   // Add another finger after 120ms and continue dragging.
   // The window should not move (see crbug.com/363625) and drag should be
   // determined by the delta of center point between the fingers.
-  generator.GestureMultiFingerScrollWithDelays(kTouchPoints, points, delays, 15,
-                                               kSteps, 150, 150);
+  generator.GestureMultiFingerScrollWithDelays(points, delays, 15, kSteps, 150,
+                                               150);
   bounds += gfx::Vector2d(150, 150);
   EXPECT_EQ(bounds.ToString(),
             toplevel->GetNativeWindow()->bounds().ToString());
@@ -306,8 +301,8 @@
   // Add third finger after 120ms and continue dragging.
   // The window should start moving but stop when the 3rd finger touches down.
   const int kEventSeparation = 15;
-  generator.GestureMultiFingerScrollWithDelays(
-      kTouchPoints, points, delays, kEventSeparation, kSteps, 150, 150);
+  generator.GestureMultiFingerScrollWithDelays(points, delays, kEventSeparation,
+                                               kSteps, 150, 150);
   int expected_drag = 150 / kSteps * 120 / kEventSeparation;
   bounds += gfx::Vector2d(expected_drag, expected_drag);
   EXPECT_EQ(bounds.ToString(),
@@ -334,8 +329,7 @@
   gfx::Rect work_area =
       display::Screen::Get()->GetDisplayNearestWindow(root_window).work_area();
   int drag_x = work_area.x() + 20 - points[0].x();
-  generator.GestureMultiFingerScroll(kTouchPoints, points, 120, kSteps, drag_x,
-                                     0);
+  generator.GestureMultiFingerScroll(points, 120, kSteps, drag_x, 0);
   EXPECT_EQ(GetDefaultSnappedWindowBoundsInParent(toplevel_window,
                                                   SnapViewType::kPrimary),
             toplevel_window->bounds());
@@ -361,8 +355,7 @@
   gfx::Rect work_area =
       display::Screen::Get()->GetDisplayNearestWindow(root_window).work_area();
   int drag_x = work_area.right() - 20 - points[0].x();
-  generator.GestureMultiFingerScroll(kTouchPoints, points, 120, kSteps, drag_x,
-                                     0);
+  generator.GestureMultiFingerScroll(points, 120, kSteps, drag_x, 0);
   EXPECT_EQ(GetDefaultSnappedWindowBoundsInParent(toplevel_window,
                                                   SnapViewType::kSecondary),
             toplevel_window->bounds());
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_menu_unittest.cc b/ash/wm/tablet_mode/tablet_mode_multitask_menu_unittest.cc
index b1ff683..51b9948 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_menu_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_menu_unittest.cc
@@ -327,8 +327,7 @@
       gfx::Point(center_x + 10, 0),
   };
   const int kSteps = 15;
-  GetEventGenerator()->GestureMultiFingerScroll(kTouchPoints, points, 15,
-                                                kSteps, 0, 150);
+  GetEventGenerator()->GestureMultiFingerScroll(points, 15, kSteps, 0, 150);
   EXPECT_TRUE(GetMultitaskMenu());
 }
 
diff --git a/ash/wm/toplevel_window_event_handler_unittest.cc b/ash/wm/toplevel_window_event_handler_unittest.cc
index 31a4dae..c110ffe 100644
--- a/ash/wm/toplevel_window_event_handler_unittest.cc
+++ b/ash/wm/toplevel_window_event_handler_unittest.cc
@@ -656,8 +656,8 @@
   // moving deltas. The window position should move along the average vector of
   // these two fingers.
   generator.GestureMultiFingerScrollWithDelays(
-      kTouchPoints, points, delta, delay_adding_finger_ms,
-      delay_releasing_finger_ms, 15, kSteps);
+      points, delta, delay_adding_finger_ms, delay_releasing_finger_ms, 15,
+      kSteps);
   bounds += gfx::Vector2d(50, 50);
   EXPECT_EQ(bounds.ToString(), target->bounds().ToString());
 }
@@ -684,8 +684,8 @@
   // Swipe right and down starting with one fingers. Add another finger at 90ms
   // and continue dragging. The drag should continue without interrupt.
   generator.GestureMultiFingerScrollWithDelays(
-      kTouchPoints, points, delta, delay_adding_finger_ms,
-      delay_releasing_finger_ms, 15, kSteps);
+      points, delta, delay_adding_finger_ms, delay_releasing_finger_ms, 15,
+      kSteps);
   bounds += gfx::Vector2d(50, 50);
   EXPECT_EQ(bounds.ToString(), target->bounds().ToString());
 }
@@ -712,8 +712,8 @@
   // Swipe right and down starting with two fingers. Remove one finger at 90ms
   // and continue dragging. The drag should continue without interrupt.
   generator.GestureMultiFingerScrollWithDelays(
-      kTouchPoints, points, delta, delay_adding_finger_ms,
-      delay_releasing_finger_ms, 15, kSteps);
+      points, delta, delay_adding_finger_ms, delay_releasing_finger_ms, 15,
+      kSteps);
   bounds += gfx::Vector2d(50, 50);
   EXPECT_EQ(bounds.ToString(), target->bounds().ToString());
 }
@@ -742,8 +742,8 @@
   // continue dragging, release second finger at 120ms and continue dragging.
   // The drag should continue without interrupt.
   generator.GestureMultiFingerScrollWithDelays(
-      kTouchPoints, points, delta, delay_adding_finger_ms,
-      delay_releasing_finger_ms, 15, kSteps);
+      points, delta, delay_adding_finger_ms, delay_releasing_finger_ms, 15,
+      kSteps);
   bounds += gfx::Vector2d(50, 50);
   EXPECT_EQ(bounds.ToString(), target->bounds().ToString());
 }
@@ -772,8 +772,8 @@
   // continue dragging, release first finger at 120ms and continue dragging.
   // The drag should continue without interrupt.
   generator.GestureMultiFingerScrollWithDelays(
-      kTouchPoints, points, delta, delay_adding_finger_ms,
-      delay_releasing_finger_ms, 15, kSteps);
+      points, delta, delay_adding_finger_ms, delay_releasing_finger_ms, 15,
+      kSteps);
   bounds += gfx::Vector2d(50, 50);
   EXPECT_EQ(bounds.ToString(), target->bounds().ToString());
 }
@@ -1438,7 +1438,7 @@
     gfx::Vector2d delta[kTouchPoints] = {gfx::Vector2d(100, 100),
                                          gfx::Vector2d(200, 200)};
 
-    gen->GestureMultiFingerScrollWithDelays(kTouchPoints, start, delta,
+    gen->GestureMultiFingerScrollWithDelays(start, delta,
                                             delay_adding_finger_ms,
                                             delay_releasing_finger_ms, 15, 100);
     base::RunLoop().RunUntilIdle();
@@ -1454,7 +1454,7 @@
     gfx::Vector2d delta[kTouchPoints] = {gfx::Vector2d(-100, -100),
                                          gfx::Vector2d(-200, -200)};
 
-    gen->GestureMultiFingerScrollWithDelays(kTouchPoints, start, delta,
+    gen->GestureMultiFingerScrollWithDelays(start, delta,
                                             delay_adding_finger_ms,
                                             delay_releasing_finger_ms, 15, 100);
     base::RunLoop().RunUntilIdle();
@@ -1486,7 +1486,7 @@
     gfx::Vector2d delta[kTouchPoints] = {gfx::Vector2d(100, 100),
                                          gfx::Vector2d(400, 400)};
 
-    gen->GestureMultiFingerScrollWithDelays(kTouchPoints, start, delta,
+    gen->GestureMultiFingerScrollWithDelays(start, delta,
                                             delay_adding_finger_ms,
                                             delay_releasing_finger_ms, 15, 100);
     base::RunLoop().RunUntilIdle();
@@ -1507,7 +1507,7 @@
     gfx::Vector2d delta[kTouchPoints] = {gfx::Vector2d(0, 0),
                                          gfx::Vector2d(-100, 0)};
 
-    gen->GestureMultiFingerScrollWithDelays(kTouchPoints, start, delta,
+    gen->GestureMultiFingerScrollWithDelays(start, delta,
                                             delay_adding_finger_ms,
                                             delay_releasing_finger_ms, 15, 100);
     base::RunLoop().RunUntilIdle();
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 73d6059..bd20a3ea 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -3398,6 +3398,7 @@
     "byte_size_unittest.cc",
     "callback_list_unittest.cc",
     "cancelable_callback_unittest.cc",
+    "check_deref_unittest.cc",
     "check_is_test_unittest.cc",
     "check_unittest.cc",
     "command_line_unittest.cc",
@@ -4455,6 +4456,7 @@
       "allocator/partition_allocator/src/partition_alloc/pointers/raw_ref_nocompile.nc",
       "byte_size_nocompile.nc",
       "callback_list_nocompile.nc",
+      "check_deref_nocompile.nc",
       "check_nocompile.nc",
       "containers/buffer_iterator_nocompile.nc",
       "containers/checked_iterators_nocompile.nc",
diff --git a/base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java b/base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java
index 84519ff..6626690 100644
--- a/base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java
+++ b/base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java
@@ -11,6 +11,7 @@
 import org.chromium.build.annotations.NullUnmarked;
 import org.chromium.build.annotations.Nullable;
 
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 /**
@@ -138,4 +139,18 @@
     @Override
     @NullUnmarked
     E get();
+
+    /**
+     * Creates an ObservableSupplier that tracks an ObservableSupplier of this ObservableSupplier.
+     */
+    default <T extends @Nullable Object> ObservableSupplier<T> createTransitive(
+            Function<E, ObservableSupplier<T>> unwrapFunction) {
+        return new TransitiveObservableSupplier<>(this, unwrapFunction);
+    }
+
+    /** Creates an ObservableSupplier that tracks a value derived from this ObservableSupplier. */
+    default <T extends @Nullable Object> ObservableSupplier<T> createDerived(
+            Function<@Nullable E, T> unwrapFunction) {
+        return new UnwrapObservableSupplier<>(this, unwrapFunction);
+    }
 }
diff --git a/base/android/java/src/org/chromium/base/supplier/TransitiveObservableSupplier.java b/base/android/java/src/org/chromium/base/supplier/TransitiveObservableSupplier.java
index 7ed733f..4e51175 100644
--- a/base/android/java/src/org/chromium/base/supplier/TransitiveObservableSupplier.java
+++ b/base/android/java/src/org/chromium/base/supplier/TransitiveObservableSupplier.java
@@ -25,7 +25,7 @@
  * @param <T> The target type that the client wants to observe.
  */
 @NullMarked
-public class TransitiveObservableSupplier<P extends @Nullable Object, T extends @Nullable Object>
+class TransitiveObservableSupplier<P extends @Nullable Object, T extends @Nullable Object>
         implements ObservableSupplier<T> {
     // Used to hold observers and current state. However the current value is only valid when there
     // are observers, otherwise is may be stale.
@@ -40,7 +40,8 @@
     // removed.
     private @Nullable ObservableSupplier<T> mCurrentTargetSupplier;
 
-    public TransitiveObservableSupplier(
+    // Create using ObservableSuppliers.createTransitive().
+    TransitiveObservableSupplier(
             ObservableSupplier<P> parentSupplier,
             Function<P, ObservableSupplier<T>> unwrapFunction) {
         mParentSupplier = parentSupplier;
diff --git a/base/android/java/src/org/chromium/base/supplier/UnwrapObservableSupplier.java b/base/android/java/src/org/chromium/base/supplier/UnwrapObservableSupplier.java
index e69ee12..d72e511 100644
--- a/base/android/java/src/org/chromium/base/supplier/UnwrapObservableSupplier.java
+++ b/base/android/java/src/org/chromium/base/supplier/UnwrapObservableSupplier.java
@@ -43,7 +43,7 @@
  * @param <T> The target type that the client wants to observe.
  */
 @NullMarked
-public class UnwrapObservableSupplier<P extends @Nullable Object, T extends @Nullable Object>
+class UnwrapObservableSupplier<P extends @Nullable Object, T extends @Nullable Object>
         implements ObservableSupplier<T> {
     private final ObservableSupplierImpl<T> mDelegateSupplier = new ObservableSupplierImpl<>();
     private final Callback<P> mOnParentSupplierChangeCallback = this::onParentSupplierChange;
@@ -54,7 +54,7 @@
      * @param parentSupplier The parent observable supplier.
      * @param unwrapFunction Converts the parent value to target value. Should handle null values.
      */
-    public UnwrapObservableSupplier(
+    UnwrapObservableSupplier(
             ObservableSupplier<P> parentSupplier, Function<@Nullable P, T> unwrapFunction) {
         mParentSupplier = parentSupplier;
         mUnwrapFunction = unwrapFunction;
diff --git a/base/android/junit/src/org/chromium/base/supplier/TransitiveObservableSupplierTest.java b/base/android/junit/src/org/chromium/base/supplier/TransitiveObservableSupplierTest.java
index a35bed3..5a260976 100644
--- a/base/android/junit/src/org/chromium/base/supplier/TransitiveObservableSupplierTest.java
+++ b/base/android/junit/src/org/chromium/base/supplier/TransitiveObservableSupplierTest.java
@@ -46,9 +46,9 @@
      * Convenience helper when the parent value needs no unwrapping. These methods should be moved
      * to the implementation file if any client needs it.
      */
-    private static <Z> TransitiveObservableSupplier<ObservableSupplier<Z>, Z> make(
+    private static <Z> ObservableSupplier<Z> make(
             ObservableSupplier<ObservableSupplier<Z>> parentSupplier) {
-        return new TransitiveObservableSupplier(parentSupplier, trampoline());
+        return parentSupplier.createTransitive(trampoline());
     }
 
     private static <T> Function<T, T> trampoline() {
diff --git a/base/android/junit/src/org/chromium/base/supplier/UnwrapObservableSupplierTest.java b/base/android/junit/src/org/chromium/base/supplier/UnwrapObservableSupplierTest.java
index 8ee2bd97..5cec7f3 100644
--- a/base/android/junit/src/org/chromium/base/supplier/UnwrapObservableSupplierTest.java
+++ b/base/android/junit/src/org/chromium/base/supplier/UnwrapObservableSupplierTest.java
@@ -48,7 +48,7 @@
             };
 
     private static ObservableSupplier<Integer> make(ObservableSupplier<Object> parentSupplier) {
-        return new UnwrapObservableSupplier(parentSupplier, UnwrapObservableSupplierTest::unwrap);
+        return parentSupplier.createDerived(UnwrapObservableSupplierTest::unwrap);
     }
 
     private static Integer unwrap(Object obj) {
diff --git a/base/apple/bundle_locations.h b/base/apple/bundle_locations.h
index adba03b..c058ac7 100644
--- a/base/apple/bundle_locations.h
+++ b/base/apple/bundle_locations.h
@@ -60,9 +60,11 @@
 // Set the bundle that the preceding functions will return, overriding the
 // default values. Restore the default by passing in `nil` or an empty
 // `FilePath`.
+BASE_EXPORT void SetOverrideMainBundlePath(const FilePath& file_path);
 BASE_EXPORT void SetOverrideOuterBundlePath(const FilePath& file_path);
 BASE_EXPORT void SetOverrideFrameworkBundlePath(const FilePath& file_path);
 #if defined(__OBJC__)
+BASE_EXPORT void SetOverrideMainBundle(NSBundle* bundle);
 BASE_EXPORT void SetOverrideOuterBundle(NSBundle* bundle);
 BASE_EXPORT void SetOverrideFrameworkBundle(NSBundle* bundle);
 #endif  // __OBJC__
diff --git a/base/apple/bundle_locations.mm b/base/apple/bundle_locations.mm
index ee3d2dc2..30555e0 100644
--- a/base/apple/bundle_locations.mm
+++ b/base/apple/bundle_locations.mm
@@ -12,12 +12,16 @@
 
 namespace {
 
+NSBundle* g_override_main_bundle = nil;
 NSBundle* g_override_framework_bundle = nil;
 NSBundle* g_override_outer_bundle = nil;
 
 }  // namespace
 
 NSBundle* MainBundle() {
+  if (g_override_main_bundle) {
+    return g_override_main_bundle;
+  }
   return NSBundle.mainBundle;
 }
 
@@ -74,6 +78,10 @@
 
 }  // namespace
 
+void SetOverrideMainBundle(NSBundle* bundle) {
+  g_override_main_bundle = bundle;
+}
+
 void SetOverrideOuterBundle(NSBundle* bundle) {
   g_override_outer_bundle = bundle;
 }
@@ -82,6 +90,11 @@
   g_override_framework_bundle = bundle;
 }
 
+void SetOverrideMainBundlePath(const FilePath& file_path) {
+  NSBundle* bundle = BundleFromPath(file_path);
+  g_override_main_bundle = bundle;
+}
+
 void SetOverrideOuterBundlePath(const FilePath& file_path) {
   NSBundle* bundle = BundleFromPath(file_path);
   g_override_outer_bundle = bundle;
diff --git a/base/at_exit.cc b/base/at_exit.cc
index 0d41961..0d042e5 100644
--- a/base/at_exit.cc
+++ b/base/at_exit.cc
@@ -30,7 +30,7 @@
 // If multiple modules instantiate AtExitManagers they'll end up living in this
 // module... they have to coexist.
 #if !defined(COMPONENT_BUILD)
-  DCHECK(!g_top_manager);
+  DCHECK(!g_top_manager || g_top_manager->allow_shadowing_);
 #endif
   g_top_manager = this;
 }
@@ -106,6 +106,11 @@
   g_disable_managers = true;
 }
 
+void AtExitManager::AllowShadowingForTesting() {
+  CHECK(g_top_manager);
+  g_top_manager->allow_shadowing_ = true;
+}
+
 AtExitManager::AtExitManager(bool shadow) : next_manager_(g_top_manager) {
   DCHECK(shadow || !g_top_manager);
   g_top_manager = this;
diff --git a/base/at_exit.h b/base/at_exit.h
index 62e547a..d19dbf2d 100644
--- a/base/at_exit.h
+++ b/base/at_exit.h
@@ -56,6 +56,11 @@
   // process mode.
   static void DisableAllAtExitManagers();
 
+  // Marks the current AtExitManager as one that can be shadowed by another.
+  // This is useful when a test wants to run code that creates its own
+  // AtExitManager, and as such ShadowingAtExitManager can't be used.
+  static void AllowShadowingForTesting();
+
  protected:
   // This constructor will allow this instance of AtExitManager to be created
   // even if one already exists.  This should only be used for testing!
@@ -74,6 +79,7 @@
 
   // Stack of managers to allow shadowing.
   const raw_ptr<AtExitManager, DanglingUntriaged> next_manager_;
+  bool allow_shadowing_ = false;
 };
 
 #if defined(UNIT_TEST)
diff --git a/base/check_deref.h b/base/check_deref.h
index fe881b29..8f943e3 100644
--- a/base/check_deref.h
+++ b/base/check_deref.h
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <utility>
+
 #include "base/check.h"
 #include "base/compiler_specific.h"
 
@@ -14,21 +16,15 @@
 
 namespace logging {
 
-// Returns a reference to pointee of `ptr` if `ptr` is not null, or dies if
-// `ptr` is null.
+// Dereferences the pointer-like object `val` if `val` doesn't test `false`, or
+// dies if it does.
 //
 // It is useful in initializers and direct assignments, where a direct `CHECK`
 // call can't be used:
 //
-//   MyType& type_ref = CHECK_DEREF(MethodReturningAPointer());
+//   MyType& type_ref = CHECK_DEREF(MethodReturningAPointerLikeObject());
 //
-// If your raw pointer is stored in a wrapped type like `unique_ptr` or
-// `raw_ptr`, you should use their `.get()` methods to get the raw pointer
-// before calling `CHECK_DEREF()`:
-//
-//   MyType& type_ref = CHECK_DEREF(your_wrapped_pointer.get());
-//
-#define CHECK_DEREF(ptr) ::logging::CheckDeref(ptr, #ptr " != nullptr")
+#define CHECK_DEREF(ptr) ::logging::CheckDeref(ptr, "*" #ptr)
 
 template <typename T>
 [[nodiscard]] T& CheckDeref(
@@ -49,6 +45,25 @@
   return *ptr;
 }
 
+template <typename T>
+[[nodiscard]] decltype(auto) CheckDeref(
+    T&& val LIFETIME_BOUND,
+    const char* message,
+    const base::Location& location = base::Location::Current()) {
+  // Note: we can't just call `CHECK(val)` here, as that would cause the error
+  // to be reported from this header, and we want the error to be reported at
+  // the file and line of the caller.
+  if (!val) [[unlikely]] {
+#if CHECK_WILL_STREAM()
+    // `CheckNoreturnError` will die with a fatal error in its destructor.
+    CheckNoreturnError::Check(message, location);
+#else
+    CheckFailure();
+#endif  // !CHECK_WILL_STREAM()
+  }
+  return *std::forward<T>(val);
+}
+
 }  // namespace logging
 
 #endif  // BASE_CHECK_DEREF_H_
diff --git a/base/check_deref_nocompile.nc b/base/check_deref_nocompile.nc
new file mode 100644
index 0000000..c7fab0f
--- /dev/null
+++ b/base/check_deref_nocompile.nc
@@ -0,0 +1,21 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is for nocompile tests.
+// It's not actually compiled, but is checked by the compiler for validity.
+
+#include "base/check_deref.h"
+
+#include <memory>
+
+namespace {
+
+void CheckDerefTemporary() {
+  // CHECK_DEREF should not extend the lifetime of temporaries.
+  // // expected-error@+1 {{temporary bound to local reference 'ref' will be destroyed at the end of the full-expression}}
+  int& ref = CHECK_DEREF(std::make_unique<int>(123));
+  (void)ref;
+}
+
+}  // namespace
diff --git a/base/check_deref_unittest.cc b/base/check_deref_unittest.cc
new file mode 100644
index 0000000..0cd2684
--- /dev/null
+++ b/base/check_deref_unittest.cc
@@ -0,0 +1,58 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/check_deref.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/test/gtest_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Test with raw pointers.
+TEST(CheckDerefTest, RawPointer) {
+  int i = 123;
+  int* ptr = &i;
+  EXPECT_EQ(&i, &CHECK_DEREF(ptr));
+
+  const int* const_ptr = &i;
+  EXPECT_EQ(&i, &CHECK_DEREF(const_ptr));
+}
+
+TEST(CheckDerefTest, RawPointerDeath) {
+  int* ptr = nullptr;
+  BASE_EXPECT_DEATH((void)CHECK_DEREF(ptr), "");
+}
+
+// Test with a smart pointer.
+TEST(CheckDerefTest, SmartPointer) {
+  auto ptr = std::make_unique<int>(123);
+  EXPECT_EQ(ptr.get(), &CHECK_DEREF(ptr));
+}
+
+TEST(CheckDerefTest, SmartPointerDeath) {
+  std::unique_ptr<int> ptr;
+  BASE_EXPECT_DEATH((void)CHECK_DEREF(ptr), "");
+}
+
+// Test with a function that returns a pointer.
+int* ReturnNull() {
+  return nullptr;
+}
+TEST(CheckDerefTest, FunctionCallDeath) {
+  BASE_EXPECT_DEATH((void)CHECK_DEREF(ReturnNull()), "");
+}
+
+// Test with move only types.
+TEST(CheckDerefTest, MoveOnlyOptionalUniquePtr) {
+  auto ptr = std::make_unique<int>(42);
+  int* raw_ptr = ptr.get();
+  std::unique_ptr<int> result = CHECK_DEREF(std::optional(std::move(ptr)));
+  EXPECT_EQ(result.get(), raw_ptr);
+  EXPECT_EQ(*result, 42);
+}
+
+}  // namespace
diff --git a/base/check_unittest.cc b/base/check_unittest.cc
index 755d9f5b6..6b3ca676 100644
--- a/base/check_unittest.cc
+++ b/base/check_unittest.cc
@@ -689,7 +689,7 @@
 
 TEST(CheckDeathTest, CheckDerefOfNullPointer) {
   std::string* null_pointer = nullptr;
-  EXPECT_CHECK("Check failed: null_pointer != nullptr. ",
+  EXPECT_CHECK("Check failed: *null_pointer. ",
                std::ignore = CHECK_DEREF(null_pointer));
 }
 
@@ -707,7 +707,7 @@
 
 TEST(CheckDeathTest, CheckDerefOfConstNullPointer) {
   std::string* const_null_pointer = nullptr;
-  EXPECT_CHECK("Check failed: const_null_pointer != nullptr. ",
+  EXPECT_CHECK("Check failed: *const_null_pointer. ",
                std::ignore = CHECK_DEREF(const_null_pointer));
 }
 
diff --git a/base/metrics/puma_histogram_functions.cc b/base/metrics/puma_histogram_functions.cc
index cb95a565..cd49b3a2 100644
--- a/base/metrics/puma_histogram_functions.cc
+++ b/base/metrics/puma_histogram_functions.cc
@@ -8,16 +8,6 @@
 
 namespace base {
 
-namespace {
-
-// Converts the given PumaType to histogram flags that should be applied to
-// records emitted with this PumaType.
-constexpr HistogramBase::Flags PumaTypeToHistogramFlags(PumaType puma_type) {
-  return static_cast<HistogramBase::Flags>(static_cast<uint16_t>(puma_type));
-}
-
-}  // namespace
-
 void PumaHistogramBoolean(PumaType puma_type,
                           std::string_view name,
                           bool sample) {
diff --git a/base/metrics/puma_histogram_functions.h b/base/metrics/puma_histogram_functions.h
index 3a2ddf3..9129355 100644
--- a/base/metrics/puma_histogram_functions.h
+++ b/base/metrics/puma_histogram_functions.h
@@ -42,6 +42,12 @@
   kRc = HistogramBase::Flags::kPumaRcTargetedHistogramFlag,
 };
 
+// Converts the given PumaType to histogram flags that should be applied to
+// records emitted with this PumaType.
+constexpr HistogramBase::Flags PumaTypeToHistogramFlags(PumaType puma_type) {
+  return static_cast<HistogramBase::Flags>(static_cast<uint16_t>(puma_type));
+}
+
 // PUMA version of base::UmaHistogramBoolean().
 BASE_EXPORT void PumaHistogramBoolean(PumaType puma_type,
                                       std::string_view name,
diff --git a/base/win/scoped_handle.h b/base/win/scoped_handle.h
index e6e90948e..93114e5e 100644
--- a/base/win/scoped_handle.h
+++ b/base/win/scoped_handle.h
@@ -72,9 +72,6 @@
 
   bool is_valid() const { return Traits::IsHandleValid(handle_); }
 
-  // TODO(crbug.com/40212898): Migrate callers to is_valid().
-  bool IsValid() const { return is_valid(); }
-
   GenericScopedHandle& operator=(GenericScopedHandle&& other) {
     DCHECK_NE(this, &other);
     Set(other.Take());
diff --git a/build/config/apple/codesign.py b/build/config/apple/codesign.py
index bb09bc49..5e2004bb 100644
--- a/build/config/apple/codesign.py
+++ b/build/config/apple/codesign.py
@@ -542,6 +542,51 @@
       ['--deep', '--preserve-metadata=identifier,entitlements,flags'])
 
 
+def VerifyLoadOrder(binary_path, expected_first_framework):
+  """Verifies that the first LC_LOAD_DYLIB in binary_path matches
+  expected_first_framework.
+  """
+  try:
+    output = subprocess.check_output(['otool', '-l', binary_path],
+                                     stderr=subprocess.STDOUT,
+                                     universal_newlines=True)
+  except subprocess.CalledProcessError as e:
+    sys.stderr.write('otool failed: %s\n' % e.output)
+    sys.exit(1)
+
+  first_dylib = None
+  lines = output.splitlines()
+  for i, line in enumerate(lines):
+    if line.strip() == 'cmd LC_LOAD_DYLIB':
+      # The name is usually a few lines down.
+      for j in range(i + 1, min(i + 10, len(lines))):
+        if lines[j].strip().startswith('name '):
+          # Extract path. Format: name /path/to/lib (offset 24)
+          parts = lines[j].strip().split(' ', 1)
+          if len(parts) > 1:
+            name_line = parts[1]
+            # Remove " (offset \d+)"
+            first_dylib = name_line.rsplit(' (offset', 1)[0]
+          break
+      if first_dylib:
+        break
+
+  if not first_dylib:
+    sys.stderr.write(
+        'Error: No LC_LOAD_DYLIB found in %s, but expected %s to be first.\n' %
+        (binary_path, expected_first_framework))
+    sys.exit(1)
+
+  # The framework path ends with .../FrameworkName.framework/FrameworkName
+  expected_suffix = '/%s.framework/%s' % (expected_first_framework,
+                                          expected_first_framework)
+  if not first_dylib.endswith(expected_suffix):
+    sys.stderr.write(
+        'Error: First LC_LOAD_DYLIB in %s is "%s", expected to end with "%s".\n'
+        % (binary_path, first_dylib, expected_suffix))
+    sys.exit(1)
+
+
 def GenerateEntitlements(path, provisioning_profile, bundle_identifier):
   """Generates an entitlements file.
 
@@ -668,6 +713,10 @@
         'are part of the bundle to codesign. The script will delete any ' +
         'files found that are not listed, and will fail if any files is ' +
         'missing.')
+    parser.add_argument(
+        '--verify-load-order-first',
+        dest='verify_load_order_first',
+        help='verify that the named framework is the first loaded dylib')
     parser.set_defaults(no_signature=False)
 
   @staticmethod
@@ -765,6 +814,9 @@
     if args.manifest:
       VerifyBundleManifest(bundle, set(args.manifest) | set(created_symlinks))
 
+    if args.verify_load_order_first:
+      VerifyLoadOrder(bundle.binary_path, args.verify_load_order_first)
+
     if args.no_signature:
       return
 
diff --git a/build/config/apple/create_signed_bundle.gni b/build/config/apple/create_signed_bundle.gni
index 3f31a1b..1ce65ed4 100644
--- a/build/config/apple/create_signed_bundle.gni
+++ b/build/config/apple/create_signed_bundle.gni
@@ -112,6 +112,10 @@
 #       the depend on it (unless the "bundle_data" target sets "product_type"
 #       to the same value as the "create_signed_bundle" target).
 #
+#   verify_as_first_framework
+#       (optional) string, if defined, verify that the named framework is the
+#       first loaded dylib in the bundle binary.
+#
 template("apple_mobile_create_signed_bundle") {
   assert(defined(invoker.product_type),
          "product_type must be defined for $target_name")
@@ -340,6 +344,14 @@
         post_processing_args += [ "-F=$_framework_path" ]
       }
     }
+
+    if (defined(invoker.verify_as_first_framework)) {
+      post_processing_args += [
+        "--verify-load-order-first",
+        invoker.verify_as_first_framework,
+      ]
+    }
+
     if (defined(invoker.partial_info_plist)) {
       _partial_info_plists = [
         invoker.primary_info_plist,
diff --git a/build/config/ios/rules.gni b/build/config/ios/rules.gni
index 02d4295..b171dc1 100644
--- a/build/config/ios/rules.gni
+++ b/build/config/ios/rules.gni
@@ -335,6 +335,17 @@
       inputs += [ orderfile_path ]
     }
 
+    if (defined(invoker.link_framework_first)) {
+      link_framework_first = invoker.link_framework_first
+      if (!defined(ldflags)) {
+        ldflags = []
+      }
+      ldflags += [
+        "-framework",
+        link_framework_first,
+      ]
+    }
+
     if (target_environment == "simulator") {
       deps += [ ":$_generate_entitlements_target" ]
 
@@ -527,6 +538,10 @@
                                "xcode_extra_attributes",
                              ])
 
+      if (defined(invoker.link_framework_first)) {
+        verify_as_first_framework = invoker.link_framework_first
+      }
+
       output_name = _output_name
       bundle_gen_dir = _variant.bundle_gen_dir
       bundle_binary_target = ":$_executable_target"
diff --git a/build/config/win/BUILD.gn b/build/config/win/BUILD.gn
index 7f8aa87..7c05183 100644
--- a/build/config/win/BUILD.gn
+++ b/build/config/win/BUILD.gn
@@ -413,6 +413,7 @@
     "/DELAYLOAD:d3d11.dll",
     "/DELAYLOAD:d3d12.dll",
     "/DELAYLOAD:d3d9.dll",
+    "/DELAYLOAD:dhcpcsvc.dll",
     "/DELAYLOAD:dcomp.dll",
     "/DELAYLOAD:dwmapi.dll",
     "/DELAYLOAD:dxgi.dll",
@@ -423,6 +424,7 @@
     "/DELAYLOAD:hid.dll",
     "/DELAYLOAD:imagehlp.dll",
     "/DELAYLOAD:imm32.dll",
+    "/DELAYLOAD:iphlpapi.dll",
     "/DELAYLOAD:mmdevapi.dll",
     "/DELAYLOAD:msi.dll",
     "/DELAYLOAD:netapi32.dll",
@@ -469,9 +471,7 @@
 config("delayloads_not_for_child_dll") {
   ldflags = [
     "/DELAYLOAD:crypt32.dll",
-    "/DELAYLOAD:dhcpcsvc.dll",
     "/DELAYLOAD:dwrite.dll",
-    "/DELAYLOAD:iphlpapi.dll",
     "/DELAYLOAD:secur32.dll",
     "/DELAYLOAD:userenv.dll",
     "/DELAYLOAD:winhttp.dll",
diff --git a/cc/metrics/compositor_frame_reporter.cc b/cc/metrics/compositor_frame_reporter.cc
index e3f2a2b1..ba869f3 100644
--- a/cc/metrics/compositor_frame_reporter.cc
+++ b/cc/metrics/compositor_frame_reporter.cc
@@ -237,49 +237,6 @@
   }
 }
 
-// For measuring the ratio of scrolling event generation, as well as arrival in
-// the Renderer. Compared to the active VSync at the time of their arrival.
-constexpr int kMaxVSyncRatioHistogramIndex =
-    kMaxGestureScrollHistogramIndex *
-    static_cast<int>(
-        CompositorFrameReporter::VSyncRatioType::kVSyncRatioTypeCount);
-const char* GetVSyncRatioTypeName(
-    CompositorFrameReporter::VSyncRatioType type) {
-  switch (type) {
-    case CompositorFrameReporter::VSyncRatioType::
-        kArrivedInRendererVsVSyncRatioAfterVSync:
-      return "ArrivedInRendererVsVSyncRatio.AfterVSync";
-    case CompositorFrameReporter::VSyncRatioType::
-        kArrivedInRendererVsVSyncRatioBeforeVSync:
-      return "ArrivedInRendererVsVSyncRatio.BeforeVSync";
-    case CompositorFrameReporter::VSyncRatioType::
-        kGenerationVsVsyncRatioAfterVSync:
-      return "GenerationVsVsyncRatio.AfterVSync";
-    case CompositorFrameReporter::VSyncRatioType::
-        kGenerationVsVsyncRatioBeforeVSync:
-      return "GenerationVsVsyncRatio.BeforeVSync";
-    case CompositorFrameReporter::VSyncRatioType::kVSyncRatioTypeCount:
-      NOTREACHED();
-  }
-}
-
-void ReportVSyncRatioMetric(const std::string& base_histogram_name,
-                            int gesture_scroll_index,
-                            CompositorFrameReporter::VSyncRatioType type,
-                            int percentage) {
-  const std::string vsync_ratio_type_name = GetVSyncRatioTypeName(type);
-  const std::string histogram_name =
-      base::JoinString({base_histogram_name, vsync_ratio_type_name}, ".");
-  STATIC_HISTOGRAM_POINTER_GROUP(
-      histogram_name,
-      gesture_scroll_index +
-          static_cast<int>(type) * kMaxGestureScrollHistogramIndex,
-      kMaxVSyncRatioHistogramIndex, Add(percentage),
-      base::LinearHistogram::FactoryGet(
-          histogram_name, 1, 100, 101,
-          base::HistogramBase::kUmaTargetedHistogramFlag));
-}
-
 #if BUILDFLAG(IS_ANDROID)
 constexpr const char kTopControlsMovedName[] = ".TopControlsMoved";
 constexpr const char kTopControlsDidNotMoveName[] = ".TopControlsDidNotMove";
@@ -1598,7 +1555,6 @@
       }
 
       if (scroll_metrics) {
-        auto& original_args = scroll_metrics->begin_frame_args();
         const base::TimeTicks browser_main_timestamp =
             event_metrics->GetDispatchStageTimestamp(
                 EventMetrics::DispatchStage::kArrivedInBrowserMain);
@@ -1621,23 +1577,6 @@
                     bucketing->max, bucketing->count,
                     base::HistogramBase::kUmaTargetedHistogramFlag));
           }
-          if (original_args.IsValid()) {
-            const base::TimeDelta generation_to_vsync_delta =
-                original_args.frame_time - generated_timestamp;
-            const double generation_to_vsync_ratio =
-                100.f * generation_to_vsync_delta / original_args.interval;
-            if (generation_to_vsync_delta.is_negative()) {
-              ReportVSyncRatioMetric(histogram_base_name, gesture_scroll_index,
-                                     CompositorFrameReporter::VSyncRatioType::
-                                         kGenerationVsVsyncRatioBeforeVSync,
-                                     std::ceil(generation_to_vsync_ratio * -1));
-            } else {
-              ReportVSyncRatioMetric(histogram_base_name, gesture_scroll_index,
-                                     CompositorFrameReporter::VSyncRatioType::
-                                         kGenerationVsVsyncRatioAfterVSync,
-                                     std::ceil(generation_to_vsync_ratio));
-            }
-          }
 
 #if BUILDFLAG(IS_ANDROID)
           ReportTopControlsMetric(histogram_base_name, top_controls_moved_,
@@ -1645,29 +1584,6 @@
                                   event_metrics->GetHistogramBucketing());
 #endif  // BUILDFLAG(IS_ANDROID)
         }
-
-        const base::TimeTicks arrived_in_renderer_timestamp =
-            event_metrics->GetDispatchStageTimestamp(
-                EventMetrics::DispatchStage::kArrivedInRendererCompositor);
-        if (original_args.IsValid() &&
-            !arrived_in_renderer_timestamp.is_null()) {
-          const base::TimeDelta arrived_after_vsync_delta =
-              arrived_in_renderer_timestamp - original_args.frame_time;
-          const double arrived_after_vsync_ratio =
-              100.f * arrived_after_vsync_delta / original_args.interval;
-          if (arrived_after_vsync_delta.is_negative()) {
-            ReportVSyncRatioMetric(
-                histogram_base_name, gesture_scroll_index,
-                CompositorFrameReporter::VSyncRatioType::
-                    kArrivedInRendererVsVSyncRatioBeforeVSync,
-                std::ceil(arrived_after_vsync_ratio * -1));
-          } else {
-            ReportVSyncRatioMetric(histogram_base_name, gesture_scroll_index,
-                                   CompositorFrameReporter::VSyncRatioType::
-                                       kArrivedInRendererVsVSyncRatioAfterVSync,
-                                   std::ceil(arrived_after_vsync_ratio));
-          }
-        }
       }
 
       // Finally, report total latency up to presentation for all event types in
diff --git a/cc/metrics/compositor_frame_reporter.h b/cc/metrics/compositor_frame_reporter.h
index 54d73c4..b466a52 100644
--- a/cc/metrics/compositor_frame_reporter.h
+++ b/cc/metrics/compositor_frame_reporter.h
@@ -173,20 +173,6 @@
     kBreakdownCount
   };
 
-  // These numbers are used for indexing UMA histograms. The order should be
-  // preserved, and entries should not be deleted.
-  //
-  // These represent ratios of stages in EventMetrics::DispatchStage to the
-  // VSync time when the event originally arrived. This can be different than
-  // the frame where this event was eventually presented.
-  enum class VSyncRatioType {
-    kArrivedInRendererVsVSyncRatioAfterVSync = 0,
-    kArrivedInRendererVsVSyncRatioBeforeVSync = 1,
-    kGenerationVsVsyncRatioAfterVSync = 2,
-    kGenerationVsVsyncRatioBeforeVSync = 3,
-    kVSyncRatioTypeCount
-  };
-
   // To distinguish between impl and main reporter
   enum class ReporterType { kImpl = 0, kMain = 1 };
 
diff --git a/cc/metrics/scroll_jank_v4_histogram_emitter.cc b/cc/metrics/scroll_jank_v4_histogram_emitter.cc
index 4e3f58b8..f9d052a 100644
--- a/cc/metrics/scroll_jank_v4_histogram_emitter.cc
+++ b/cc/metrics/scroll_jank_v4_histogram_emitter.cc
@@ -9,11 +9,11 @@
 #include <utility>
 
 #include "base/check_op.h"
-#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/notreached.h"
 #include "base/trace_event/trace_event.h"
 #include "cc/metrics/event_metrics.h"
+#include "cc/metrics/histogram_macros.h"
 #include "cc/metrics/scroll_jank_dropped_frame_tracker.h"
 
 namespace cc {
@@ -179,12 +179,14 @@
                               kVsyncCountsMin, kVsyncCountsMax,
                               kVsyncCountsBuckets);
 
-  for (int i = 0; i <= static_cast<int>(JankReason::kMaxValue); i++) {
+  constexpr int kMaxJankReasonIndex = static_cast<int>(JankReason::kMaxValue);
+  for (int i = 0; i <= kMaxJankReasonIndex; i++) {
     JankReason reason = static_cast<JankReason>(i);
     int delayed_frames_for_reason = fixed_window_.delayed_frames_per_reason[i];
     DCHECK_LE(delayed_frames_for_reason, fixed_window_.delayed_frames);
-    base::UmaHistogramPercentage(
-        GetDelayedFramesPercentageFixedWindow4HistogramName(reason),
+    STATIC_HISTOGRAM_PERCENTAGE_POINTER_GROUP(
+        GetDelayedFramesPercentageFixedWindow4HistogramName(reason), i,
+        kMaxJankReasonIndex + 1,
         (100 * delayed_frames_for_reason) / kHistogramEmitFrequency);
   }
 
diff --git a/cc/slim/frame_sink_impl.cc b/cc/slim/frame_sink_impl.cc
index 26d3ad68..e25ecc1 100644
--- a/cc/slim/frame_sink_impl.cc
+++ b/cc/slim/frame_sink_impl.cc
@@ -217,8 +217,7 @@
                                        bool is_lost) {
   auto itr = uploaded_resources_.find(ui_resource_id);
   CHECK(itr != uploaded_resources_.end());
-  auto* sii = context_provider_->SharedImageInterface();
-  sii->DestroySharedImage(sync_token, std::move(itr->second.shared_image));
+  itr->second.shared_image->UpdateDestructionSyncToken(sync_token);
   uploaded_resources_.erase(itr);
 }
 
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 1aba539..401cb0a 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/android/config.gni")
+import("//build/config/chrome_build.gni")
 import("//build/config/cronet/config.gni")
 import("//build/toolchain/gcc_toolchain.gni")
 import("//chrome/android/chrome_common_shared_library.gni")
@@ -262,6 +263,10 @@
       deps += [ "//components/plus_addresses/android:java_resources" ]
     }
 
+    if (is_chrome_branded && defined(autofill_payments_resource_target)) {
+      deps += [ autofill_payments_resource_target ]
+    }
+
     if (enable_screen_capture) {
       deps += [ "//chrome/browser:screen_capture_java_resources" ]
     }
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java
index bcde794..6e0eea2f 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java
@@ -66,9 +66,11 @@
 
 /** Integration tests for autofill keyboard accessory. */
 // TODO(crbug.com/447076444): Enable Keyboard Accessory revamp flag
+// TODO(crbug.com/462636368): Turn on the dynamic positioning flag after blink bug is fixed.
 @RunWith(ChromeJUnit4ClassRunner.class)
 @Batch(Batch.PER_CLASS)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@DisableFeatures(ChromeFeatureList.AUTOFILL_ANDROID_KEYBOARD_ACCESSORY_DYNAMIC_POSITIONING)
 public class AutofillKeyboardAccessoryIntegrationTest {
     @Rule
     public FreshCtaTransitTestRule mActivityTestRule =
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorMediator.java
index e267f43..89ed3fd 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorMediator.java
@@ -391,13 +391,16 @@
         mTabListCoordinator.cleanupTabGridView();
         mVisibleTabs.clear();
         mVisibleTabGroups.clear();
-        mResetHandler.resetWithListOfTabs(
-                /* tabs= */ null,
-                /* tabGroupSyncIds= */ null,
-                /* recyclerViewPosition= */ null,
-                /* quickMode= */ false);
-        mModel.set(TabListEditorProperties.IS_VISIBLE, false);
-        mResetHandler.postHiding();
+
+        if (mCreationMode != CreationMode.ITEM_PICKER) {
+            mResetHandler.resetWithListOfTabs(
+                    /* tabs= */ null,
+                    /* tabGroupSyncIds= */ null,
+                    /* recyclerViewPosition= */ null,
+                    /* quickMode= */ false);
+            mModel.set(TabListEditorProperties.IS_VISIBLE, false);
+            mResetHandler.postHiding();
+        }
         if (mLifecycleObserver != null) mLifecycleObserver.didHide();
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java
index 47e0ef25..e862da2 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java
@@ -38,7 +38,6 @@
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.SyncOneshotSupplier;
 import org.chromium.base.supplier.SyncOneshotSupplierImpl;
-import org.chromium.base.supplier.TransitiveObservableSupplier;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
 import org.chromium.build.BuildConfig;
@@ -141,11 +140,9 @@
             mTabSwitcherPaneCoordinatorSupplier = new ObservableSupplierImpl<>();
 
     @SuppressWarnings("NullAway") // Generics are not null propagated nicely.
-    private final TransitiveObservableSupplier<@Nullable TabSwitcherPaneCoordinator, Boolean>
-            mHandleBackPressChangedSupplier =
-                    new TransitiveObservableSupplier<>(
-                            mTabSwitcherPaneCoordinatorSupplier,
-                            pc -> pc.getHandleBackPressChangedSupplier());
+    private final ObservableSupplier<Boolean> mHandleBackPressChangedSupplier =
+            mTabSwitcherPaneCoordinatorSupplier.createTransitive(
+                    TabSwitcherPaneCoordinator::getHandleBackPressChangedSupplier);
 
     private final FrameLayout mRootView;
     private final TabSwitcherPaneCoordinatorFactory mFactory;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneMediator.java
index 46abfac6..81e6fb9 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneMediator.java
@@ -29,7 +29,6 @@
 import org.chromium.base.supplier.LazyOneshotSupplier;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
-import org.chromium.base.supplier.TransitiveObservableSupplier;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.hub.HubUtils;
@@ -169,8 +168,7 @@
     private final AnimationHandler mSupplementaryContainerAnimationHandler = new AnimationHandler();
     private @Nullable ObservableSupplier<TabListEditorController> mTabListEditorControllerSupplier;
     private final ObservableSupplierImpl<Boolean> mHubSearchBoxVisibilitySupplier;
-    private @Nullable TransitiveObservableSupplier<TabListEditorController, Boolean>
-            mCurrentTabListEditorControllerBackSupplier;
+    private @Nullable ObservableSupplier<Boolean> mCurrentTabListEditorControllerBackSupplier;
     private @Nullable View mCustomView;
     private @Nullable Runnable mCustomViewBackPressRunnable;
     private final @Px int mSearchBoxGapPx;
@@ -407,11 +405,8 @@
                 : "setTabListEditorControllerSupplier should be called only once.";
         mTabListEditorControllerSupplier = tabListEditorControllerSupplier;
         mCurrentTabListEditorControllerBackSupplier =
-                new TransitiveObservableSupplier<>(
-                        tabListEditorControllerSupplier,
-                        tabListEditorController -> {
-                            return tabListEditorController.getHandleBackPressChangedSupplier();
-                        });
+                tabListEditorControllerSupplier.createTransitive(
+                        BackPressHandler::getHandleBackPressChangedSupplier);
         mCurrentTabListEditorControllerBackSupplier.addObserver(mNotifyBackPressedCallback);
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TransitiveSharedGroupObserver.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TransitiveSharedGroupObserver.java
index 36749d3..5d77a3a 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TransitiveSharedGroupObserver.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TransitiveSharedGroupObserver.java
@@ -10,7 +10,6 @@
 import org.chromium.base.lifetime.Destroyable;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
-import org.chromium.base.supplier.TransitiveObservableSupplier;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.components.collaboration.CollaborationService;
@@ -28,20 +27,15 @@
  *
  * <p>This class abstracts away the record keeping that would otherwise be required to register and
  * unregister observers and create a new {@link SharedGroupObserver} whenever a different tab group
- * id needs to be observed. See {@link TransitiveObservableSupplier} for details on how the
- * underlying observer chaining works.
+ * id needs to be observed.
  */
 @NullMarked
 public class TransitiveSharedGroupObserver implements Destroyable {
     private final ObservableSupplierImpl<@Nullable SharedGroupObserver>
             mCurrentSharedGroupObserverSupplier = new ObservableSupplierImpl<>();
-    private final TransitiveObservableSupplier<@Nullable SharedGroupObserver, @Nullable Integer>
-            mGroupSharedStateSupplier;
-    private final TransitiveObservableSupplier<
-                    @Nullable SharedGroupObserver, @Nullable List<GroupMember>>
-            mGroupMembersSupplier;
-    private final TransitiveObservableSupplier<@Nullable SharedGroupObserver, @Nullable String>
-            mCollaborationIdSupplier;
+    private final ObservableSupplier<@Nullable Integer> mGroupSharedStateSupplier;
+    private final ObservableSupplier<@Nullable List<GroupMember>> mGroupMembersSupplier;
+    private final ObservableSupplier<@Nullable String> mCollaborationIdSupplier;
     private final TabGroupSyncService mTabGroupSyncService;
     private final DataSharingService mDataSharingService;
     private final CollaborationService mCollaborationService;
@@ -63,16 +57,13 @@
         mCollaborationService = collaborationService;
 
         mGroupSharedStateSupplier =
-                new TransitiveObservableSupplier<>(
-                        mCurrentSharedGroupObserverSupplier,
+                mCurrentSharedGroupObserverSupplier.createTransitive(
                         SharedGroupObserver::getGroupSharedStateSupplier);
         mGroupMembersSupplier =
-                new TransitiveObservableSupplier<>(
-                        mCurrentSharedGroupObserverSupplier,
+                mCurrentSharedGroupObserverSupplier.createTransitive(
                         SharedGroupObserver::getGroupMembersSupplier);
         mCollaborationIdSupplier =
-                new TransitiveObservableSupplier<>(
-                        mCurrentSharedGroupObserverSupplier,
+                mCurrentSharedGroupObserverSupplier.createTransitive(
                         SharedGroupObserver::getCollaborationIdSupplier);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 335ae35..86af454 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -64,7 +64,6 @@
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.supplier.SupplierUtils;
 import org.chromium.base.supplier.UnownedUserDataSupplier;
-import org.chromium.base.supplier.UnwrapObservableSupplier;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
 import org.chromium.build.annotations.Nullable;
@@ -966,9 +965,9 @@
                         });
 
         ObservableSupplier<Boolean> incognitoSupplier =
-                new UnwrapObservableSupplier<>(
-                        mTabModelSelector.getCurrentTabModelSupplier(),
-                        (tabModel) -> tabModel == null ? false : tabModel.isIncognito());
+                mTabModelSelector
+                        .getCurrentTabModelSupplier()
+                        .createDerived(tabModel -> tabModel != null && tabModel.isIncognito());
         HubLayoutDependencyHolder hubLayoutDependencyHolder =
                 new HubLayoutDependencyHolder(
                         mHubProvider.getHubManagerSupplier(),
@@ -3231,6 +3230,11 @@
             }
 
             @Override
+            public void showTipsNotificationsChannelSettings() {
+                TipsUtils.launchTipsNotificationsSettings(getContext());
+            }
+
+            @Override
             public int getTabCountForRelaunchFromSharedPrefs() {
                 return MultiWindowUtils.getTabCountForRelaunchFromSharedPrefs(mWindowId);
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/TabStateStore.java b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/TabStateStore.java
index f3665be..47de866 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/TabStateStore.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/TabStateStore.java
@@ -13,13 +13,17 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
+import org.chromium.build.annotations.EnsuresNonNull;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.CollectionSaveForwarder;
+import org.chromium.chrome.browser.tab.CollectionStorageObserverFactory;
+import org.chromium.chrome.browser.tab.StorageCollectionSynchronizer;
 import org.chromium.chrome.browser.tab.StorageLoadedData;
 import org.chromium.chrome.browser.tab.StorageLoadedData.LoadedTabState;
+import org.chromium.chrome.browser.tab.StorageRestoreOrchestratorFactory;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabId;
 import org.chromium.chrome.browser.tab.TabState;
@@ -31,7 +35,6 @@
 import org.chromium.chrome.browser.tabmodel.TabGroupModelFilterObserver;
 import org.chromium.chrome.browser.tabmodel.TabGroupVisualDataStore;
 import org.chromium.chrome.browser.tabmodel.TabModel;
-import org.chromium.chrome.browser.tabmodel.TabModelObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabRegistrationObserver;
 import org.chromium.chrome.browser.tabmodel.TabPersistentStore;
@@ -39,9 +42,7 @@
 import org.chromium.components.tabs.TabStripCollection;
 
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
 
 /** Orchestrates saving of tabs to the {@link TabStateStorageService}. */
 @NullMarked
@@ -59,9 +60,10 @@
     private final Map<Token, CollectionSaveForwarder> mGroupForwarderMap = new HashMap<>();
 
     private @Nullable TabModelSelectorTabRegistrationObserver mTabRegistrationObserver;
-    private @Nullable TabMoveObserver mTabMoveObserver;
     private @Nullable TabGroupModelFilter mFilter;
+    private @Nullable StorageCollectionSynchronizer mSynchronizer;
     private int mRestoredTabCount;
+    private boolean mIsDestroyed;
 
     private class InnerRegistrationObserver
             implements TabModelSelectorTabRegistrationObserver.Observer {
@@ -76,24 +78,6 @@
         }
     }
 
-    private class TabMoveObserver implements TabModelObserver {
-        private final TabModel mTabModel;
-
-        private TabMoveObserver(TabModel tabModel) {
-            mTabModel = tabModel;
-            mTabModel.addObserver(this);
-        }
-
-        private void destroy() {
-            mTabModel.removeObserver(this);
-        }
-
-        @Override
-        public void didMoveTab(Tab tab, int newIndex, int curIndex) {
-            onMoveTab(mTabModel, newIndex, curIndex);
-        }
-    }
-
     private final TabGroupModelFilterObserver mVisualDataUpdateObserver =
             new TabGroupModelFilterObserver() {
                 @Override
@@ -164,13 +148,11 @@
     }
 
     private void catchUpAndBeginTracking() {
-        assert mTabRegistrationObserver == null && mTabMoveObserver == null;
+        assert mTabRegistrationObserver == null;
         mTabRegistrationObserver = new TabModelSelectorTabRegistrationObserver(mTabModelSelector);
         mTabRegistrationObserver.addObserverAndNotifyExistingTabRegistration(
                 new InnerRegistrationObserver());
 
-        mTabMoveObserver = new TabMoveObserver(mTabModelSelector.getModel(/* incognito= */ false));
-
         mFilter =
                 mTabModelSelector
                         .getTabGroupModelFilterProvider()
@@ -241,12 +223,12 @@
 
     @Override
     public void destroy() {
+        assert !mIsDestroyed;
+        mIsDestroyed = true;
+
         if (mTabRegistrationObserver != null) {
             mTabRegistrationObserver.destroy();
         }
-        if (mTabMoveObserver != null) {
-            mTabMoveObserver.destroy();
-        }
 
         for (CollectionSaveForwarder forwarder : mGroupForwarderMap.values()) {
             forwarder.destroy();
@@ -254,6 +236,10 @@
         if (mFilter != null) {
             mFilter.removeTabGroupObserver(mVisualDataUpdateObserver);
         }
+
+        if (mSynchronizer != null) {
+            mSynchronizer.destroy();
+        }
     }
 
     @Override
@@ -323,30 +309,6 @@
         // TODO(https://crbug.com/430996004): If closing, delete the tab record.
     }
 
-    private void onMoveTab(TabModel tabModel, int newIndex, int curIndex) {
-        // TODO(https://crbug.com/427254267): Add some sort of debouncing to avoid duplicate
-        // and/or redundant saves when an operation with multiple events/moves.
-        // TODO(https://crbug.com/427254267): A collections implementation will need pinned
-        // and unpinned collections, but this is at the wrong scope to know about that.
-        int start = Math.max(0, Math.min(newIndex, curIndex));
-        int end = Math.min(tabModel.getCount() - 1, Math.max(newIndex, curIndex));
-        Set<Token> tabGroupsToSave = new HashSet<>();
-        for (int i = start; i <= end; i++) {
-            Tab child = tabModel.getTabAt(i);
-            Token groupId = child == null ? null : child.getTabGroupId();
-            if (groupId != null) {
-                tabGroupsToSave.add(groupId);
-            }
-        }
-        for (Token groupId : tabGroupsToSave) {
-            // TODO(https://crbug.com/427254267): Save the tab group's children index list.
-
-            // Useless call to avoid compiler complaining until actually used.
-            groupId.toBundle();
-        }
-        // TODO(https://crbug.com/427254267): Save the tab model's children index list.
-    }
-
     private void loadAllTabsFromService() {
         long loadStartTime = SystemClock.elapsedRealtime();
         // TODO(crbug.com/458335579): Figure out incognito.
@@ -367,6 +329,7 @@
 
         if (ChromeFeatureList.sTabStorageSqlitePrototypeAuthoritativeReadSource.getValue()) {
             TabGroupVisualDataStore.cacheGroups(data.getGroupsData());
+            initRestoreOrchestrator(data);
         }
 
         if (mRestoredTabCount == 0) {
@@ -384,6 +347,11 @@
      * @param data The data to restore tabs from.
      */
     private void restoreActiveTab(StorageLoadedData data) {
+        if (mIsDestroyed) {
+            cleanupStorageLoadedData(data);
+            return;
+        }
+
         LoadedTabState[] loadedTabStates = data.getLoadedTabStates();
         assert loadedTabStates.length > 0;
 
@@ -427,7 +395,6 @@
         Tab tab = resolveTab(loadedTabState.tabState, tabId, index);
         if (tab == null) return;
 
-        loadedTabState.onTabCreationCallback.onResult(tab);
         // TODO(https://crbug.com/451624258): This is the opposite order of creation and details
         // from how the previous implementation did it. Verify this doesn't break anything.
         for (TabPersistentStoreObserver observer : mObservers) {
@@ -456,6 +423,11 @@
             StorageLoadedData data, int restoredActiveTabIndex, int startIndex, int batchSize) {
         assert startIndex >= 0;
         assert batchSize > 0;
+        if (mIsDestroyed) {
+            cleanupStorageLoadedData(data);
+            return;
+        }
+
         LoadedTabState[] loadedTabStates = data.getLoadedTabStates();
         int endIndex = Math.min(startIndex + batchSize, loadedTabStates.length);
 
@@ -478,21 +450,32 @@
     }
 
     private void onFinishedCreatingAllTabs(StorageLoadedData data) {
-        if (ChromeFeatureList.sTabStorageSqlitePrototypeAuthoritativeReadSource.getValue()) {
-            TabGroupVisualDataStore.removeCachedGroups(data.getGroupsData());
-        }
+        cleanupStorageLoadedData(data);
+        data = null;
+
+        if (mIsDestroyed) return;
+
+        initCollectionTracking();
 
         for (TabPersistentStoreObserver observer : mObservers) {
             observer.onStateLoaded();
         }
 
         if (!ChromeFeatureList.sTabStorageSqlitePrototypeAuthoritativeReadSource.getValue()) {
+            catchUpAndBeginTracking();
+        }
+    }
+
+    private void cleanupStorageLoadedData(StorageLoadedData data) {
+        if (!ChromeFeatureList.sTabStorageSqlitePrototypeAuthoritativeReadSource.getValue()) {
             // When we aren't the authoritative source we don't trust ourselves to be correct.
             // Raze the db and rebuild from the loaded tab state to ensure we are in a known good
             // state. This is a no-op if we are the authoritative source as there shouldn't be a
             // delta and if there is we need a less blunt mechanism to reconcile the difference.
             clearState();
-            catchUpAndBeginTracking();
+        }
+        if (ChromeFeatureList.sTabStorageSqlitePrototypeAuthoritativeReadSource.getValue()) {
+            TabGroupVisualDataStore.removeCachedGroups(data.getGroupsData());
         }
         data.destroy();
     }
@@ -514,13 +497,13 @@
     }
 
     private void initVisualDataTracking() {
-        if (mFilter == null) return;
+        assert mFilter != null;
 
         TabStripCollection collection = mFilter.getTabModel().getTabStripCollection();
-        if (collection == null) return;
+        assert collection != null;
 
         Profile profile = mFilter.getTabModel().getProfile();
-        if (profile == null) return;
+        assert profile != null;
 
         // Add forwarders for untracked groups.
         for (Token groupId : mFilter.getAllTabGroupIds()) {
@@ -531,4 +514,48 @@
 
         mFilter.addTabGroupObserver(mVisualDataUpdateObserver);
     }
+
+    @EnsuresNonNull("mSynchronizer")
+    private void maybeInitSynchronizer() {
+        if (mSynchronizer != null) return;
+
+        // TODO(https://crbug.com/451614469): Watch for incognito as well, eventually.
+        TabModel tabModel = mTabModelSelector.getModel(/* incognito= */ false);
+
+        TabStripCollection tabStripCollection = tabModel.getTabStripCollection();
+        assert tabStripCollection != null;
+
+        Profile profile = tabModel.getProfile();
+        assert profile != null;
+
+        mSynchronizer = new StorageCollectionSynchronizer(profile, tabStripCollection);
+    }
+
+    private void initRestoreOrchestrator(StorageLoadedData data) {
+        maybeInitSynchronizer();
+
+        TabModel tabModel = mTabModelSelector.getModel(/* incognito= */ false);
+
+        Profile profile = tabModel.getProfile();
+        assert profile != null;
+
+        TabStripCollection tabStripCollection = tabModel.getTabStripCollection();
+        assert tabStripCollection != null;
+
+        StorageRestoreOrchestratorFactory factory =
+                new StorageRestoreOrchestratorFactory(profile, tabStripCollection, data);
+        mSynchronizer.consumeRestoreOrchestratorFactory(factory);
+    }
+
+    private void initCollectionTracking() {
+        maybeInitSynchronizer();
+
+        TabModel tabModel = mTabModelSelector.getModel(/* incognito= */ false);
+
+        Profile profile = tabModel.getProfile();
+        assert profile != null;
+
+        CollectionStorageObserverFactory factory = new CollectionStorageObserverFactory(profile);
+        mSynchronizer.consumeCollectionObserverFactory(factory);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewBackgroundTabAnimationHostView.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewBackgroundTabAnimationHostView.java
index 81ed4e5..8a98c1a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewBackgroundTabAnimationHostView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewBackgroundTabAnimationHostView.java
@@ -23,7 +23,6 @@
 
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.theme.ThemeUtils;
 import org.chromium.chrome.browser.toolbar.top.ToggleTabStackButton;
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.components.browser_ui.styles.ChromeColors;
@@ -79,6 +78,27 @@
         mRunOnNextLayoutDelegate = new RunOnNextLayoutDelegate(this);
     }
 
+    /**
+     * Determines the {@link AnimationType} based on the current state.
+     *
+     * @param tabSwitcherButtonIsVisible True if the tab switcher button is visible.
+     * @param isNtp True if the current tab is the regular Ntp.
+     * @param ntpToolbarTransitionPercentage To know the current transition percentage of the ntp
+     *     search box.
+     */
+    public static @AnimationType int calculateAnimationType(
+            boolean tabSwitcherButtonIsVisible,
+            boolean isNtp,
+            float ntpToolbarTransitionPercentage) {
+        if (tabSwitcherButtonIsVisible || !isNtp) {
+            return AnimationType.DEFAULT;
+        } else {
+            return ntpToolbarTransitionPercentage == 1f
+                    ? AnimationType.NTP_FULL_SCROLL
+                    : AnimationType.NTP_PARTIAL_SCROLL;
+        }
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -137,38 +157,39 @@
      * Prepares the animation.
      *
      * @param tabSwitcherButton The real Tab Switcher Button.
-     * @param isNtp True if the current tab is the regular Ntp.
+     * @param tabSwitcherRect The {@link Rect} of the tab switcher button.
      * @param isIncognito True if the current tab is an incognito tab.
      * @param isTopToolbar True if current tab has a top toolbar.
      * @param backgroundColor The current color of the toolbar.
+     * @param animationType The {@link AnimationType}.
+     * @param brandedColorScheme The {@link BrandedColorScheme} for the toolbar.
      * @param tabCount The tab count to display.
      * @param toolbarHeight Current height of the toolbar in the screen (absolute y-coordinate in
      *     the screen).
      * @param statusBarHeight The status bar height to calculate the y-offset within the screen.
      * @param xOffset Offset for cases where the screen can't draw from x = 0.
-     * @param ntpToolbarTransitionPercentage To know if the search box is in the toolbar position.
      */
     /* package */ void setUpAnimation(
             ToggleTabStackButton tabSwitcherButton,
-            boolean isNtp,
+            Rect tabSwitcherRect,
             boolean isIncognito,
             boolean isTopToolbar,
             @ColorInt int backgroundColor,
+            @AnimationType int animationType,
+            @BrandedColorScheme int brandedColorScheme,
             int tabCount,
             int toolbarHeight,
             int statusBarHeight,
-            int xOffset,
-            float ntpToolbarTransitionPercentage) {
+            int xOffset) {
+
+        mAnimationType = animationType;
         mStatusBarHeight = statusBarHeight;
         mXOffset = xOffset;
         mIsTopToolbar = isTopToolbar;
         mFakeTabSwitcherButton.setTabCount(tabCount, isIncognito);
+        mFakeTabSwitcherButton.setBrandedColorScheme(brandedColorScheme);
 
         Context context = getContext();
-        @BrandedColorScheme
-        int brandedColorScheme =
-                ThemeUtils.getBrandedColorScheme(context, backgroundColor, isIncognito);
-        mFakeTabSwitcherButton.setBrandedColorScheme(brandedColorScheme);
         if (ColorUtils.inNightMode(context)) {
             mLinkIcon.setImageTintList(ChromeColors.getPrimaryIconTint(context, isIncognito));
             @ColorInt
@@ -181,27 +202,20 @@
             roundedRect.setColor(color);
         }
 
-        Rect tabSwitcherRect = new Rect();
-        boolean tabSwitcherButtonIsVisible =
-                tabSwitcherButton.getGlobalVisibleRect(tabSwitcherRect);
         int horizontalMargin = tabSwitcherRect.left - xOffset;
         int verticalMargin = toolbarHeight - statusBarHeight;
 
-        if (tabSwitcherButtonIsVisible || !isNtp) {
-            mAnimationType = AnimationType.DEFAULT;
+        if (mAnimationType == AnimationType.DEFAULT) {
             mFakeTabSwitcherButton.setButtonColor(backgroundColor);
             mFakeTabSwitcherButton.setNotificationIconStatus(
                     tabSwitcherButton.shouldShowNotificationIcon());
         } else {
             mFakeTabSwitcherButton.setUpNtpAnimation(/* incrementCount= */ true);
-            if (ntpToolbarTransitionPercentage == 1f) {
-                mAnimationType = AnimationType.NTP_FULL_SCROLL;
+            if (mAnimationType == AnimationType.NTP_FULL_SCROLL) {
                 verticalMargin +=
                         Math.round(
                                 context.getResources()
                                         .getDimension(R.dimen.toolbar_height_no_shadow));
-            } else {
-                mAnimationType = AnimationType.NTP_PARTIAL_SCROLL;
             }
         }
         mFakeTabSwitcherButton.setMargin(verticalMargin, horizontalMargin);
@@ -277,7 +291,7 @@
     }
 
     /* package */ @AnimationType
-    int getAnimationType() {
+    int getAnimationTypeForTesting() {
         return mAnimationType;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewBackgroundTabAnimationHostViewUnitTest.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewBackgroundTabAnimationHostViewUnitTest.java
index c8e4de6..d860c4b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewBackgroundTabAnimationHostViewUnitTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewBackgroundTabAnimationHostViewUnitTest.java
@@ -6,8 +6,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.when;
 
 import android.animation.Animator;
@@ -21,7 +19,6 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
-import androidx.annotation.ColorInt;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
 import org.junit.Before;
@@ -36,9 +33,9 @@
 import org.chromium.base.MathUtils;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.compositor.layouts.phone.NewBackgroundTabAnimationHostView.AnimationType;
 import org.chromium.chrome.browser.toolbar.top.ToggleTabStackButton;
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
-import org.chromium.components.browser_ui.styles.ChromeColors;
 import org.chromium.components.browser_ui.styles.SemanticColorUtils;
 import org.chromium.ui.base.TestActivity;
 
@@ -90,28 +87,56 @@
     }
 
     @Test
-    public void testSetUpAnimation_Default() {
-        // Default for non-NTP tab (button visibility at the start of the animation is irrelevant).
-        setButtonVisibility(false);
-        when(mTabSwitcherButton.shouldShowNotificationIcon()).thenReturn(true);
+    public void testCalculateAnimationType() {
+        assertEquals(
+                AnimationType.DEFAULT,
+                NewBackgroundTabAnimationHostView.calculateAnimationType(
+                        /* tabSwitcherButtonIsVisible= */ true,
+                        /* isNtp= */ false,
+                        /* ntpToolbarTransitionPercentage= */ 0f));
+        assertEquals(
+                AnimationType.DEFAULT,
+                NewBackgroundTabAnimationHostView.calculateAnimationType(
+                        /* tabSwitcherButtonIsVisible= */ true,
+                        /* isNtp= */ true,
+                        /* ntpToolbarTransitionPercentage= */ 0f));
+        assertEquals(
+                AnimationType.NTP_PARTIAL_SCROLL,
+                NewBackgroundTabAnimationHostView.calculateAnimationType(
+                        /* tabSwitcherButtonIsVisible= */ false,
+                        /* isNtp= */ true,
+                        /* ntpToolbarTransitionPercentage= */ 0f));
+        assertEquals(
+                AnimationType.NTP_PARTIAL_SCROLL,
+                NewBackgroundTabAnimationHostView.calculateAnimationType(
+                        /* tabSwitcherButtonIsVisible= */ false,
+                        /* isNtp= */ true,
+                        /* ntpToolbarTransitionPercentage= */ 0.5f));
+        assertEquals(
+                AnimationType.NTP_FULL_SCROLL,
+                NewBackgroundTabAnimationHostView.calculateAnimationType(
+                        /* tabSwitcherButtonIsVisible= */ false,
+                        /* isNtp= */ true,
+                        /* ntpToolbarTransitionPercentage= */ 1f));
+    }
 
+    @Test
+    public void testSetUpAnimation_Default() {
+        when(mTabSwitcherButton.shouldShowNotificationIcon()).thenReturn(true);
         mHostView.setUpAnimation(
                 mTabSwitcherButton,
-                /* isNtp= */ false,
+                mTabSwitcherRect,
                 /* isIncognito= */ false,
                 /* isTopToolbar= */ false,
-                /* backgroundColor= */ ChromeColors.getDefaultThemeColor(
-                        mActivity, /* isIncognito= */ false),
+                /* backgroundColor= */ Color.CYAN,
+                NewBackgroundTabAnimationHostView.AnimationType.DEFAULT,
+                BrandedColorScheme.APP_DEFAULT,
                 /* tabCount= */ 12,
                 /* toolbarHeight= */ 30,
                 /* statusBarHeight= */ 5,
-                /* xOffset= */ 3,
-                /* ntpToolbarTransitionPercentage= */ 1f);
+                /* xOffset= */ 3);
 
         assertDefaultSettings(
-                /* buttonColor= */ ChromeColors.getDefaultThemeColor(
-                        mActivity, /* isIncognito= */ false),
-                BrandedColorScheme.APP_DEFAULT,
                 /* tabCount= */ 12,
                 /* topMargin= */ 25,
                 /* leftMargin= */ 47,
@@ -120,36 +145,32 @@
         when(mTabSwitcherButton.shouldShowNotificationIcon()).thenReturn(false);
         mHostView.setUpAnimation(
                 mTabSwitcherButton,
-                /* isNtp= */ false,
+                mTabSwitcherRect,
                 /* isIncognito= */ false,
                 /* isTopToolbar= */ false,
-                /* backgroundColor= */ ChromeColors.getDefaultThemeColor(
-                        mActivity, /* isIncognito= */ false),
+                /* backgroundColor= */ Color.CYAN,
+                NewBackgroundTabAnimationHostView.AnimationType.DEFAULT,
+                BrandedColorScheme.APP_DEFAULT,
                 /* tabCount= */ 12,
                 /* toolbarHeight= */ 7,
                 /* statusBarHeight= */ 10,
-                /* xOffset= */ 3,
-                /* ntpToolbarTransitionPercentage= */ 1f);
+                /* xOffset= */ 3);
         assertFalse(mFakeTabSwitcherButton.getShowIconNotificationStatusForTesting());
 
-        // Default for NTP.
-        setButtonVisibility(true);
         mHostView.setUpAnimation(
                 mTabSwitcherButton,
-                /* isNtp= */ true,
+                mTabSwitcherRect,
                 /* isIncognito= */ false,
                 /* isTopToolbar= */ false,
-                /* backgroundColor= */ ChromeColors.getDefaultThemeColor(
-                        mActivity, /* isIncognito= */ false),
+                /* backgroundColor= */ Color.CYAN,
+                NewBackgroundTabAnimationHostView.AnimationType.DEFAULT,
+                BrandedColorScheme.APP_DEFAULT,
                 /* tabCount= */ 56,
                 /* toolbarHeight= */ 94,
                 /* statusBarHeight= */ 10,
-                /* xOffset= */ 5,
-                /* ntpToolbarTransitionPercentage= */ 1f);
+                /* xOffset= */ 5);
 
         assertDefaultSettings(
-                ChromeColors.getDefaultThemeColor(mActivity, /* isIncognito= */ false),
-                BrandedColorScheme.APP_DEFAULT,
                 /* tabCount= */ 56,
                 /* topMargin= */ 84,
                 /* leftMargin= */ 45,
@@ -158,21 +179,21 @@
 
     @Test
     public void testSetUpAnimation_NtpPartialScroll() {
-        setButtonVisibility(false);
         mHostView.setUpAnimation(
                 mTabSwitcherButton,
-                /* isNtp= */ true,
+                mTabSwitcherRect,
                 /* isIncognito= */ false,
                 /* isTopToolbar= */ false,
                 /* backgroundColor= */ Color.CYAN,
+                NewBackgroundTabAnimationHostView.AnimationType.NTP_PARTIAL_SCROLL,
+                BrandedColorScheme.APP_DEFAULT,
                 /* tabCount= */ 38,
                 /* toolbarHeight= */ 7,
                 /* statusBarHeight= */ 3,
-                /* xOffset= */ 1,
-                /* ntpToolbarTransitionPercentage= */ 0.5f);
+                /* xOffset= */ 1);
 
         assertNtpSettings(
-                NewBackgroundTabAnimationHostView.AnimationType.NTP_PARTIAL_SCROLL,
+                AnimationType.NTP_PARTIAL_SCROLL,
                 /* tabCount= */ 38,
                 /* topMargin= */ 4,
                 /* leftMargin= */ 49);
@@ -180,20 +201,20 @@
 
     @Test
     public void testSetUpAnimation_NtpFullScroll() {
-        setButtonVisibility(false);
         mHostView.setUpAnimation(
                 mTabSwitcherButton,
-                /* isNtp= */ true,
+                mTabSwitcherRect,
                 /* isIncognito= */ false,
                 /* isTopToolbar= */ false,
                 /* backgroundColor= */ Color.CYAN,
+                NewBackgroundTabAnimationHostView.AnimationType.NTP_FULL_SCROLL,
+                BrandedColorScheme.APP_DEFAULT,
                 /* tabCount= */ 9,
                 /* toolbarHeight= */ 12,
                 /* statusBarHeight= */ 10,
-                /* xOffset= */ 15,
-                /* ntpToolbarTransitionPercentage= */ 1f);
+                /* xOffset= */ 15);
         assertNtpSettings(
-                NewBackgroundTabAnimationHostView.AnimationType.NTP_FULL_SCROLL,
+                AnimationType.NTP_FULL_SCROLL,
                 /* tabCount= */ 9,
                 /* topMargin= */ 2,
                 /* leftMargin= */ 35);
@@ -206,18 +227,18 @@
 
     @Test
     public void testGetAnimatorSet_Default() {
-        setButtonVisibility(true);
         mHostView.setUpAnimation(
                 mTabSwitcherButton,
-                /* isNtp= */ false,
+                mTabSwitcherRect,
                 /* isIncognito= */ false,
                 /* isTopToolbar= */ false,
                 /* backgroundColor= */ Color.CYAN,
+                NewBackgroundTabAnimationHostView.AnimationType.DEFAULT,
+                BrandedColorScheme.APP_DEFAULT,
                 /* tabCount= */ 9,
                 /* toolbarHeight= */ 0,
                 /* statusBarHeight= */ 0,
-                /* xOffset= */ 0,
-                /* ntpToolbarTransitionPercentage= */ 1f);
+                /* xOffset= */ 0);
 
         AnimatorSet animatorSet = mHostView.getAnimatorSet(/* originX= */ -1, /* originY= */ -1);
         ArrayList<Animator> animators = animatorSet.getChildAnimations();
@@ -228,18 +249,18 @@
 
     @Test
     public void testGetAnimatorSet_NtpPartialScroll() {
-        setButtonVisibility(false);
         mHostView.setUpAnimation(
                 mTabSwitcherButton,
-                /* isNtp= */ true,
+                mTabSwitcherRect,
                 /* isIncognito= */ false,
                 /* isTopToolbar= */ false,
                 /* backgroundColor= */ Color.CYAN,
+                NewBackgroundTabAnimationHostView.AnimationType.NTP_PARTIAL_SCROLL,
+                BrandedColorScheme.APP_DEFAULT,
                 /* tabCount= */ 9,
                 /* toolbarHeight= */ 0,
                 /* statusBarHeight= */ 0,
-                /* xOffset= */ 0,
-                /* ntpToolbarTransitionPercentage= */ 0.4f);
+                /* xOffset= */ 0);
 
         AnimatorSet animatorSet = mHostView.getAnimatorSet(/* originX= */ -1, /* originY= */ -1);
         ArrayList<Animator> animators = animatorSet.getChildAnimations();
@@ -250,18 +271,18 @@
 
     @Test
     public void testGetAnimatorSet_NtpFullScroll() {
-        setButtonVisibility(false);
         mHostView.setUpAnimation(
                 mTabSwitcherButton,
-                /* isNtp= */ true,
+                mTabSwitcherRect,
                 /* isIncognito= */ false,
                 /* isTopToolbar= */ false,
                 /* backgroundColor= */ Color.CYAN,
+                NewBackgroundTabAnimationHostView.AnimationType.NTP_FULL_SCROLL,
+                BrandedColorScheme.APP_DEFAULT,
                 /* tabCount= */ 9,
                 /* toolbarHeight= */ 0,
                 /* statusBarHeight= */ 0,
-                /* xOffset= */ 0,
-                /* ntpToolbarTransitionPercentage= */ 1f);
+                /* xOffset= */ 0);
         AnimatorSet animatorSet = mHostView.getAnimatorSet(/* originX= */ -1, /* originY= */ -1);
         ArrayList<Animator> animators = animatorSet.getChildAnimations();
         assertEquals(4, animators.size());
@@ -270,85 +291,20 @@
     }
 
     @Test
-    public void testBrandedColorScheme() {
-        setButtonVisibility(true);
-        mHostView.setUpAnimation(
-                mTabSwitcherButton,
-                /* isNtp= */ false,
-                /* isIncognito= */ false,
-                /* isTopToolbar= */ false,
-                /* backgroundColor= */ ChromeColors.getDefaultThemeColor(
-                        mActivity, /* isIncognito= */ false),
-                /* tabCount= */ 0,
-                /* toolbarHeight= */ 0,
-                /* statusBarHeight= */ 0,
-                /* xOffset= */ 0,
-                /* ntpToolbarTransitionPercentage= */ 1f);
-        assertEquals(
-                BrandedColorScheme.APP_DEFAULT,
-                mFakeTabSwitcherButton.getBrandedColorSchemeForTesting());
-
-        mHostView.setUpAnimation(
-                mTabSwitcherButton,
-                /* isNtp= */ false,
-                /* isIncognito= */ true,
-                /* isTopToolbar= */ false,
-                /* backgroundColor= */ ChromeColors.getDefaultThemeColor(
-                        mActivity, /* isIncognito= */ true),
-                /* tabCount= */ 0,
-                /* toolbarHeight= */ 0,
-                /* statusBarHeight= */ 0,
-                /* xOffset= */ 0,
-                /* ntpToolbarTransitionPercentage= */ 1f);
-        assertEquals(
-                BrandedColorScheme.INCOGNITO,
-                mFakeTabSwitcherButton.getBrandedColorSchemeForTesting());
-
-        mHostView.setUpAnimation(
-                mTabSwitcherButton,
-                /* isNtp= */ false,
-                /* isIncognito= */ false,
-                /* isTopToolbar= */ false,
-                /* backgroundColor= */ Color.GREEN,
-                /* tabCount= */ 0,
-                /* toolbarHeight= */ 0,
-                /* statusBarHeight= */ 0,
-                /* xOffset= */ 0,
-                /* ntpToolbarTransitionPercentage= */ 1f);
-        assertEquals(
-                BrandedColorScheme.LIGHT_BRANDED_THEME,
-                mFakeTabSwitcherButton.getBrandedColorSchemeForTesting());
-
-        mHostView.setUpAnimation(
-                mTabSwitcherButton,
-                /* isNtp= */ false,
-                /* isIncognito= */ false,
-                /* isTopToolbar= */ false,
-                /* backgroundColor= */ Color.RED,
-                /* tabCount= */ 0,
-                /* toolbarHeight= */ 0,
-                /* statusBarHeight= */ 0,
-                /* xOffset= */ 0,
-                /* ntpToolbarTransitionPercentage= */ 1f);
-        assertEquals(
-                BrandedColorScheme.DARK_BRANDED_THEME,
-                mFakeTabSwitcherButton.getBrandedColorSchemeForTesting());
-    }
-
-    @Test
     @Config(qualifiers = "night")
     public void testNightMode() {
         mHostView.setUpAnimation(
                 mTabSwitcherButton,
-                /* isNtp= */ false,
+                mTabSwitcherRect,
                 /* isIncognito= */ false,
                 /* isTopToolbar= */ false,
-                /* backgroundColor= */ Color.GREEN,
+                /* backgroundColor= */ Color.CYAN,
+                NewBackgroundTabAnimationHostView.AnimationType.DEFAULT,
+                BrandedColorScheme.APP_DEFAULT,
                 /* tabCount= */ 0,
                 /* toolbarHeight= */ 0,
                 /* statusBarHeight= */ 0,
-                /* xOffset= */ 0,
-                /* ntpToolbarTransitionPercentage= */ 0f);
+                /* xOffset= */ 0);
 
         GradientDrawable roundedRect = (GradientDrawable) mLinkIconView.getBackground();
         assertEquals(
@@ -356,22 +312,7 @@
                 roundedRect.getColor());
     }
 
-    private void setButtonVisibility(boolean isVisible) {
-        doAnswer(
-                        invocation -> {
-                            Rect rect = invocation.getArgument(0);
-                            rect.set(mTabSwitcherRect);
-                            return isVisible;
-                        })
-                .when(mTabSwitcherButton)
-                .getGlobalVisibleRect(any());
-    }
-
-    private void assertCommonElements(
-            @NewBackgroundTabAnimationHostView.AnimationType int animationType,
-            int tabCount,
-            int leftMargin) {
-        assertEquals(animationType, mHostView.getAnimationType());
+    private void assertCommonElements(int tabCount, int leftMargin) {
         assertEquals(tabCount, mFakeTabSwitcherButton.getTabCountForTesting());
 
         FrameLayout.LayoutParams params =
@@ -380,16 +321,11 @@
     }
 
     private void assertDefaultSettings(
-            @ColorInt int buttonColor,
-            @BrandedColorScheme int brandedColorScheme,
-            int tabCount,
-            int topMargin,
-            int leftMargin,
-            boolean showNotificationIcon) {
-        assertCommonElements(
-                NewBackgroundTabAnimationHostView.AnimationType.DEFAULT, tabCount, leftMargin);
-        assertEquals(buttonColor, mFakeTabSwitcherButton.getButtonColorForTesting());
-        assertEquals(brandedColorScheme, mFakeTabSwitcherButton.getBrandedColorSchemeForTesting());
+            int tabCount, int topMargin, int leftMargin, boolean showNotificationIcon) {
+        assertEquals(
+                NewBackgroundTabAnimationHostView.AnimationType.DEFAULT,
+                mHostView.getAnimationTypeForTesting());
+        assertCommonElements(tabCount, leftMargin);
         assertEquals(
                 showNotificationIcon,
                 mFakeTabSwitcherButton.getShowIconNotificationStatusForTesting());
@@ -401,15 +337,12 @@
     }
 
     private void assertNtpSettings(
-            @NewBackgroundTabAnimationHostView.AnimationType int animationType,
-            int tabCount,
-            int topMargin,
-            int leftMargin) {
+            @AnimationType int animationType, int tabCount, int topMargin, int leftMargin) {
         FrameLayout.LayoutParams params =
                 (FrameLayout.LayoutParams) mFakeTabSwitcherButton.getLayoutParams();
         // For Ntp, the tabCount increases when calling {@link
         // mFakeTabSwitcherButton#setUpNtpAnimation}.
-        assertCommonElements(animationType, tabCount + 1, leftMargin);
+        assertCommonElements(tabCount + 1, leftMargin);
         int height = topMargin;
         if (animationType == NewBackgroundTabAnimationHostView.AnimationType.NTP_FULL_SCROLL) {
             height +=
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewBackgroundTabFakeTabSwitcherButton.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewBackgroundTabFakeTabSwitcherButton.java
index 7118984..911ab76 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewBackgroundTabFakeTabSwitcherButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewBackgroundTabFakeTabSwitcherButton.java
@@ -66,7 +66,6 @@
     private ImageView mTabSwitcherButtonView;
     private TabSwitcherDrawable mTabSwitcherDrawable;
 
-    private @BrandedColorScheme int mBrandedColorScheme;
     private int mTabCount;
     private boolean mIsIncognito;
 
@@ -80,13 +79,14 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mBrandedColorScheme = BrandedColorScheme.LIGHT_BRANDED_THEME;
         mTabCount = 0;
         mIsIncognito = false;
         mTabSwitcherDrawable =
                 TabSwitcherDrawable.createTabSwitcherDrawable(
-                        getContext(), mBrandedColorScheme, TabSwitcherDrawableLocation.TAB_TOOLBAR);
-        setBrandedColorScheme(mBrandedColorScheme);
+                        getContext(),
+                        BrandedColorScheme.LIGHT_BRANDED_THEME,
+                        TabSwitcherDrawableLocation.TAB_TOOLBAR);
+        setBrandedColorScheme(BrandedColorScheme.LIGHT_BRANDED_THEME);
         setTabCount(mTabCount, mIsIncognito);
         setNotificationIconStatus(false);
 
@@ -96,7 +96,6 @@
     }
 
     /* package */ void setBrandedColorScheme(@BrandedColorScheme int brandedColorScheme) {
-        mBrandedColorScheme = brandedColorScheme;
         mTabSwitcherDrawable.setTint(
                 ThemeUtils.getThemedToolbarIconTint(getContext(), brandedColorScheme));
         mTabSwitcherDrawable.setNotificationBackground(brandedColorScheme);
@@ -303,11 +302,6 @@
         mRunOnNextLayoutDelegate.runOnNextLayoutRunnables();
     }
 
-    /* package */ @BrandedColorScheme
-    int getBrandedColorSchemeForTesting() {
-        return mBrandedColorScheme;
-    }
-
     /* package */ int getTabCountForTesting() {
         return mTabCount;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayout.java
index f86724c..0eb429ab 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayout.java
@@ -9,6 +9,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.content.Context;
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -59,10 +60,12 @@
 import org.chromium.chrome.browser.tab_ui.TabContentManager;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
+import org.chromium.chrome.browser.theme.ThemeUtils;
 import org.chromium.chrome.browser.toolbar.CustomTabCount;
 import org.chromium.chrome.browser.toolbar.ToolbarManager;
 import org.chromium.chrome.browser.toolbar.ToolbarPositionController;
 import org.chromium.chrome.browser.toolbar.top.ToggleTabStackButton;
+import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.components.browser_ui.widget.animation.CancelAwareAnimatorListener;
 import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.components.sensitive_content.SensitiveContentClient;
@@ -105,6 +108,9 @@
     private @Nullable NewBackgroundTabAnimationHostView mBackgroundHostView;
     private @Nullable NewForegroundTabAnimationHostView mForegroundHostView;
     private @Nullable AnimatorSet mTabCreatedBackgroundAnimation;
+    // The real tab switcher button view. This is used to update the view visibility based on the
+    // background animation progress.
+    private @Nullable View mTabSwitcherButton;
     private @Nullable TopInsetCoordinator mTopInsetCoordinator;
     private @Nullable Runnable mAnimationRunnable;
     private @Nullable Runnable mTimeoutRunnable;
@@ -688,7 +694,7 @@
         boolean isIncognito = animationTab.isIncognitoBranded();
         assert assumeNonNull(mLayoutTabs).length == 1;
         mSkipForceAnimationToFinish = true;
-        startHiding();
+        forceHidingImmediatelyIfNeeded(isRegularNtp);
 
         if (!isRegularNtp && mBrowserControlsVisibilityToken == TokenHolder.INVALID_TOKEN) {
             mBrowserControlsVisibilityToken = mBrowserVisibilityDelegate.showControlsPersistent();
@@ -697,6 +703,9 @@
         ToggleTabStackButton tabSwitcherButton =
                 mAnimationHostView.findViewById(R.id.tab_switcher_button);
         assert tabSwitcherButton != null;
+        Rect tabSwitcherRect = new Rect();
+        boolean tabSwitcherButtonIsVisible =
+                tabSwitcherButton.getGlobalVisibleRect(tabSwitcherRect);
 
         Context context = getContext();
         mBackgroundHostView =
@@ -709,34 +718,58 @@
         assumeNonNull(mTabModelSelector);
         int prevTabCount = mTabModelSelector.getModel(isIncognito).getCount() - 1;
         mCustomTabCountToken = mCustomTabCount.setCount(prevTabCount);
+
         @ColorInt
         int toolbarColor =
                 isRegularNtp
                         ? NewTabAnimationUtils.getBackgroundColor(context, isIncognito)
                         : mToolbarManager.getPrimaryColor();
-
         int[] toolbarPosition = new int[2];
         mAnimationHostView.findViewById(R.id.toolbar).getLocationInWindow(toolbarPosition);
-        Rect compositorViewRect = new Rect();
-        mCompositorViewHolder.getGlobalVisibleRect(compositorViewRect);
         boolean isTopToolbar =
                 isRegularNtp || ToolbarPositionController.shouldShowToolbarOnTop(animationTab);
+        int toolbarHeight = toolbarPosition[1] + getTopInsetIfNeeded(animationTab);
+
+        Rect compositorViewRect = new Rect();
+        mCompositorViewHolder.getGlobalVisibleRect(compositorViewRect);
+
         ObservableSupplier<Float> ntpSearchBoxTransitionPercentageSupplier =
                 mToolbarManager.getNtpSearchBoxTransitionPercentageSupplier();
 
-        int toolbarHeight = toolbarPosition[1] + getTopInsetIfNeeded(animationTab);
+        @AnimationType
+        int animationType =
+                NewBackgroundTabAnimationHostView.calculateAnimationType(
+                        tabSwitcherButtonIsVisible,
+                        isRegularNtp,
+                        ntpSearchBoxTransitionPercentageSupplier.get());
+
+        @BrandedColorScheme int brandedColorScheme;
+        if (isRegularNtp
+                && animationType == AnimationType.DEFAULT
+                && NtpCustomizationUtils.shouldAdjustIconTintForNtp(/* isTablet= */ false)) {
+            brandedColorScheme = BrandedColorScheme.DARK_BRANDED_THEME;
+        } else {
+            brandedColorScheme =
+                    ThemeUtils.getBrandedColorScheme(context, toolbarColor, isIncognito);
+        }
+
+        if (mTopInsetCoordinator != null && animationType == AnimationType.DEFAULT) {
+            mTabSwitcherButton = tabSwitcherButton;
+            toolbarColor = Color.TRANSPARENT;
+        }
 
         mBackgroundHostView.setUpAnimation(
                 tabSwitcherButton,
-                isRegularNtp,
+                tabSwitcherRect,
                 isIncognito,
                 isTopToolbar,
                 toolbarColor,
+                animationType,
+                brandedColorScheme,
                 prevTabCount,
                 toolbarHeight,
                 compositorViewRect.top,
-                compositorViewRect.left,
-                ntpSearchBoxTransitionPercentageSupplier.get());
+                compositorViewRect.left);
 
         // {@link View#INVISIBLE} is needed to generate the geometry information.
         mBackgroundHostView.setVisibility(View.INVISIBLE);
@@ -760,7 +793,6 @@
                     mTimeoutRunnable = null;
                     assumeNonNull(mTabModelSelector);
                     assumeNonNull(mBackgroundHostView);
-                    @AnimationType int animationType = mBackgroundHostView.getAnimationType();
                     boolean shouldObserveNtp =
                             isRegularNtp && animationType == AnimationType.DEFAULT;
                     AnimationInterruptor interruptor =
@@ -786,6 +818,10 @@
                                 @Override
                                 public void onStart(Animator animation) {
                                     checker.onAnimationStart();
+
+                                    if (mTabSwitcherButton != null) {
+                                        mTabSwitcherButton.setVisibility(View.INVISIBLE);
+                                    }
                                     // Release custom tab count as soon as the animation starts to
                                     // avoid showing the old tab count if the user decides to scroll
                                     // up during AnimationType.NTP_PARTIAL_SCROLL or
@@ -840,8 +876,6 @@
 
         if (visibilitySupplier.get()) {
             visibilitySupplier.addObserver(mVisibilityObserver);
-            // TODO(crbug.com/40282469): Check with UX about the NTP bottom sheet's scrim taking a
-            // bit to disappear and decrease the timeout.
             mHandler.postDelayed(mTimeoutRunnable, ANIMATION_TIMEOUT_MS);
         } else {
             setRunOnNextLayout(mBackgroundHostView, mAnimationRunnable);
@@ -849,6 +883,10 @@
     }
 
     private void cleanUpBackgroundAnimation() {
+        if (mTabSwitcherButton != null) {
+            mTabSwitcherButton.setVisibility(View.VISIBLE);
+            mTabSwitcherButton = null;
+        }
         mTabCreatedBackgroundAnimation = null;
         mAnimationHostView.removeView(mBackgroundHostView);
         mBackgroundHostView = null;
@@ -867,6 +905,11 @@
         return 0;
     }
 
+    private void forceHidingImmediatelyIfNeeded(boolean isNtp) {
+        startHiding();
+        if (mTopInsetCoordinator != null && isNtp) doneHiding();
+    }
+
     private void onTopInsetCoordinatorAvailable(TopInsetCoordinator topInsetCoordinator) {
         mTopInsetCoordinatorSupplier.removeObserver(mTopInsetCoordinatorObserver);
         mTopInsetCoordinator = topInsetCoordinator;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayoutUnitTest.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayoutUnitTest.java
index 0e4aced..9da2f546 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayoutUnitTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayoutUnitTest.java
@@ -410,7 +410,6 @@
 
         ShadowLooper.runUiThreadTasks();
 
-        assertFalse(mNewTabAnimationLayout.isRunningAnimations());
         verify(mAnimationHostView, times(1))
                 .removeView(any(NewBackgroundTabAnimationHostView.class));
         verify(mTabModelSelector, never()).selectModel(false);
@@ -418,10 +417,38 @@
     }
 
     @Test
-    public void testOnTabCreated_tabCreatedInBackground_ntpToken() {
-        when(mCurrentTab.getUrl()).thenReturn(new GURL("chrome://newtab"));
-        when(mCurrentTab.getNativePage()).thenReturn(mNtp);
+    public void testOnTabCreated_tabCreatedInBackground_forceHidingImmediatelyIfNeeded() {
+        mNewTabAnimationLayout.onTabCreated(
+                FAKE_TIME,
+                NEW_TAB_ID,
+                /* index= */ 1,
+                CURRENT_TAB_ID,
+                /* newIsIncognito= */ false,
+                /* background= */ true,
+                /* originX= */ 0f,
+                /* originY= */ 0f);
+        assertTrue(
+                "Layout should be starting to hide, but not hidden.",
+                mNewTabAnimationLayout.isStartingToHide());
 
+        setNtp();
+        mNewTabAnimationLayout.onTabCreated(
+                FAKE_TIME,
+                NEW_TAB_ID,
+                /* index= */ 1,
+                CURRENT_TAB_ID,
+                /* newIsIncognito= */ false,
+                /* background= */ true,
+                /* originX= */ 0f,
+                /* originY= */ 0f);
+        assertFalse(
+                "Layout should have immediately hidden.",
+                mNewTabAnimationLayout.isStartingToHide());
+    }
+
+    @Test
+    public void testOnTabCreated_tabCreatedInBackground_ntpToken() {
+        setNtp();
         mNewTabAnimationLayout.onTabCreated(
                 FAKE_TIME,
                 NEW_TAB_ID,
@@ -469,4 +496,9 @@
 
         verify(mBrowserVisibilityDelegate, times(1)).releasePersistentShowingToken(1);
     }
+
+    private void setNtp() {
+        when(mCurrentTab.getUrl()).thenReturn(new GURL("chrome://newtab"));
+        when(mCurrentTab.getNativePage()).thenReturn(mNtp);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
index 5eae6cb..56464cb4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
@@ -1118,6 +1118,17 @@
     }
 
     @Override
+    public void updateOffsetTagsInfo(@Nullable BrowserControlsOffsetTagsInfo offsetTagsInfo) {
+        if (ChromeFeatureList.sBrowserControlsInViz.isEnabled() && offsetTagsInfo != null) {
+            // Use the content OffsetTag here, because the tab strip and content are part of
+            // the same subtree and move together with the same offset.
+            mTabStripTreeProvider.updateOffsetTag(offsetTagsInfo.getContentOffsetTag());
+        } else {
+            mTabStripTreeProvider.updateOffsetTag(null);
+        }
+    }
+
+    @Override
     public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
         // TODO (crbug/328055199): Check if losing focus to a non-Chrome task.
         if (!mIsHeaderCustomizationSupported) return;
@@ -1532,12 +1543,7 @@
                             BrowserControlsOffsetTagsInfo oldOffsetTagsInfo,
                             BrowserControlsOffsetTagsInfo offsetTagsInfo,
                             @BrowserControlsState int constraints) {
-                        if (ChromeFeatureList.sBrowserControlsInViz.isEnabled()) {
-                            // Use the content OffsetTag here, because the tab strip and content
-                            // are part of the same subtree and move together with the same offset.
-                            mTabStripTreeProvider.updateOffsetTag(
-                                    offsetTagsInfo.getContentOffsetTag());
-                        }
+                        updateOffsetTagsInfo(offsetTagsInfo);
                     }
 
                     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
index ee6328c..07663acf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
@@ -265,7 +265,6 @@
             int mediaIndicatorTint =
                     layoutHelper.getMediaIndicatorTintColor(mediaState, closeButtonTint);
 
-            // TODO(crbug.com/326301060): Update tab outline placeholder color with color picker.
             TabStripSceneLayerJni.get()
                     .putStripTabLayer(
                             mNativePtr,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
index 564b895..85164e2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
@@ -15,6 +15,7 @@
 import static org.chromium.ui.listmenu.ListMenuItemProperties.TEXT_APPEARANCE_ID;
 import static org.chromium.ui.listmenu.ListMenuItemProperties.TITLE;
 
+import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -32,6 +33,7 @@
 import androidx.browser.customtabs.CustomContentAction;
 import androidx.browser.customtabs.CustomTabsIntent;
 
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
@@ -339,7 +341,20 @@
     static class PendingIntentSender {
         public void send(PendingIntent pendingIntent, Context context, int code, Intent intent)
                 throws PendingIntent.CanceledException {
-            pendingIntent.send(context, code, intent);
+            ActivityOptions options = ActivityOptions.makeBasic();
+            ApiCompatibilityUtils.setActivityOptionsBackgroundActivityStartAllowAlways(options);
+            pendingIntent.send(
+                    context,
+                    code,
+                    intent,
+                    /** onFinished= */
+                    null,
+                    /** handler= */
+                    null,
+                    /** requiredPermissions= */
+                    null,
+                    /** options= */
+                    options.toBundle());
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeBottomSheetCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeBottomSheetCoordinator.java
index 55f067bc..786ad482 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeBottomSheetCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeBottomSheetCoordinator.java
@@ -81,6 +81,15 @@
                 ReaderModeBottomSheetProperties.CONTENT_VIEW,
                 ReaderModePrefsView.create(mContext, mDomDistillerService.getDistilledPagePrefs()));
 
+        // Expand the peeked bottom sheet when tapped.
+        mReaderModeBottomSheetView.setOnClickListener(
+                view -> {
+                    if (mBottomSheetController.getSheetState()
+                            == BottomSheetController.SheetState.PEEK) {
+                        mBottomSheetController.expandSheet();
+                    }
+                });
+
         mThemeColorObserver =
                 (color, shouldAnimate) -> {
                     updateThemeProperties();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/tips/TipsOptInCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/tips/TipsOptInCoordinator.java
index c0c60cc..7ebfc447 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/tips/TipsOptInCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/tips/TipsOptInCoordinator.java
@@ -5,8 +5,6 @@
 package org.chromium.chrome.browser.notifications.tips;
 
 import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
 import android.view.LayoutInflater;
 import android.view.View;
 
@@ -18,7 +16,6 @@
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
@@ -26,9 +23,6 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
 import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
-import org.chromium.components.browser_ui.notifications.BaseNotificationManagerProxyFactory;
-import org.chromium.components.browser_ui.notifications.NotificationProxyUtils;
-import org.chromium.components.browser_ui.notifications.channels.ChannelsInitializer;
 import org.chromium.ui.widget.ButtonCompat;
 
 import java.lang.annotation.Retention;
@@ -58,7 +52,6 @@
 
     // LINT.ThenChange(//tools/metrics/histograms/metadata/notifications/enums.xml:TipsNotificationsOptInPromoEventType)
 
-    private final Context mContext;
     private final BottomSheetController mBottomSheetController;
     private final TipsOptInSheetContent mSheetContent;
 
@@ -69,7 +62,6 @@
      * @param bottomSheetController The system {@link BottomSheetController}.
      */
     public TipsOptInCoordinator(Context context, BottomSheetController bottomSheetController) {
-        mContext = context;
         mBottomSheetController = bottomSheetController;
 
         View contentView =
@@ -80,7 +72,7 @@
         ButtonCompat positiveButtonView = contentView.findViewById(R.id.opt_in_positive_button);
         positiveButtonView.setOnClickListener(
                 (view) -> {
-                    launchNotificationSettings();
+                    TipsUtils.launchTipsNotificationsSettings(context);
                     mBottomSheetController.hideContent(mSheetContent, /* animate= */ true);
                     recordOptInPromoEventType(OptInPromoEventType.ACCEPTED);
                 });
@@ -106,38 +98,6 @@
         recordOptInPromoEventType(OptInPromoEventType.SHOWN);
     }
 
-    private Intent getNotificationSettingsIntent() {
-        Intent intent = new Intent();
-        if (areAppNotificationsEnabled()) {
-            intent.setAction(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
-            intent.putExtra(Settings.EXTRA_APP_PACKAGE, mContext.getPackageName());
-            intent.putExtra(Settings.EXTRA_CHANNEL_ID, ChromeChannelDefinitions.ChannelId.TIPS);
-        } else {
-            intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
-            intent.putExtra(Settings.EXTRA_APP_PACKAGE, mContext.getPackageName());
-        }
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        return intent;
-    }
-
-    private boolean areAppNotificationsEnabled() {
-        return NotificationProxyUtils.areNotificationsEnabled();
-    }
-
-    private void createNotificationChannel() {
-        new ChannelsInitializer(
-                        BaseNotificationManagerProxyFactory.create(),
-                        ChromeChannelDefinitions.getInstance(),
-                        mContext.getResources())
-                .ensureInitialized(ChromeChannelDefinitions.ChannelId.TIPS);
-    }
-
-    private void launchNotificationSettings() {
-        // Make sure the channel is initialized before sending users to the settings.
-        createNotificationChannel();
-        mContext.startActivity(getNotificationSettingsIntent());
-    }
-
     private void recordOptInPromoEventType(@OptInPromoEventType int type) {
         RecordHistogram.recordEnumeratedHistogram(
                 "Notifications.Tips.OptInPromo.EventType", type, OptInPromoEventType.NUM_ENTRIES);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/tips/TipsUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/tips/TipsUtils.java
index 78af6651..e8e2ff3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/tips/TipsUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/tips/TipsUtils.java
@@ -6,7 +6,9 @@
 
 import android.app.NotificationManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
+import android.provider.Settings;
 
 import androidx.annotation.StringRes;
 
@@ -31,6 +33,7 @@
 import org.chromium.chrome.browser.profiles.ProfileProvider;
 import org.chromium.components.browser_ui.notifications.BaseNotificationManagerProxyFactory;
 import org.chromium.components.browser_ui.notifications.NotificationProxyUtils;
+import org.chromium.components.browser_ui.notifications.channels.ChannelsInitializer;
 import org.chromium.ui.base.WindowAndroid;
 
 import java.util.ArrayList;
@@ -228,6 +231,43 @@
     }
 
     /**
+     * Launch the settings page for the Tips Notifications channel.
+     *
+     * @param context The current context.
+     */
+    public static void launchTipsNotificationsSettings(Context context) {
+        // Make sure the channel is initialized before sending users to the settings.
+        createNotificationChannel(context);
+        context.startActivity(getNotificationSettingsIntent(context));
+    }
+
+    private static Intent getNotificationSettingsIntent(Context context) {
+        Intent intent = new Intent();
+        if (areAppNotificationsEnabled()) {
+            intent.setAction(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
+            intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
+            intent.putExtra(Settings.EXTRA_CHANNEL_ID, ChromeChannelDefinitions.ChannelId.TIPS);
+        } else {
+            intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+            intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
+        }
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        return intent;
+    }
+
+    private static boolean areAppNotificationsEnabled() {
+        return NotificationProxyUtils.areNotificationsEnabled();
+    }
+
+    private static void createNotificationChannel(Context context) {
+        new ChannelsInitializer(
+                        BaseNotificationManagerProxyFactory.create(),
+                        ChromeChannelDefinitions.getInstance(),
+                        context.getResources())
+                .ensureInitialized(ChromeChannelDefinitions.ChannelId.TIPS);
+    }
+
+    /**
      * @return Whether the opt-in promo should be always shown for testing tips.
      */
     public static boolean shouldAlwaysShowOptInPromo() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/search/SearchResultsPreferenceFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/search/SearchResultsPreferenceFragment.java
index d16a1ab..bb01365 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/settings/search/SearchResultsPreferenceFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/search/SearchResultsPreferenceFragment.java
@@ -66,48 +66,17 @@
         setPreferenceScreen(screen);
 
         String prevGroup = null;
-        String mainSettings = MainSettings.class.getName();
-        int entrySize = mPreferenceData.size();
-        for (int i = 0; i < entrySize; ++i) {
-            SettingsIndexData.Entry info = mPreferenceData.get(i);
+        for (SettingsIndexData.Entry info : mPreferenceData) {
             String group = info.header;
-            System.out.println(
-                    "crdebug i: "
-                            + i
-                            + " prev: "
-                            + prevGroup
-                            + " group: "
-                            + group
-                            + " i+1: "
-                            + (i + 1 < entrySize ? mPreferenceData.get(i + 1).header : "x"));
+
             // The results are grouped by the top level setting categories. Build the category
             // header above the group.
             if (!TextUtils.equals(group, prevGroup)) {
-                // Skip a single-entry group
-                // header: Appearance                    removed (header)
-                //  entry: Appearance             =>      entry: Appearance
-                // header: About Chrome                  header: About Chrome
-                //  entry: Application version            entry: Application version
-                if ((i + 1 >= entrySize
-                                || !TextUtils.equals(group, mPreferenceData.get(i + 1).header))
-                        && TextUtils.equals(mainSettings, info.parentFragment)) {
-                    System.out.println("crdebug show as a entry: " + group);
-                    var headerPref = new PreferenceCategory(requireContext());
-                    headerPref.setIconSpaceReserved(false);
-                    screen.addPreference(headerPref);
-                } else {
-                    var headerPref = new PreferenceCategory(requireContext());
-                    headerPref.setTitle(group);
-                    headerPref.setIconSpaceReserved(false);
-                    screen.addPreference(headerPref);
-                }
+                PreferenceCategory prefGroup = new PreferenceCategory(requireContext());
+                prefGroup.setTitle(group);
+                prefGroup.setIconSpaceReserved(false);
+                screen.addPreference(prefGroup);
             }
-
-            // Do not show the top-level entry since it looks duplicated with the header.
-            // Example:
-            // header: Toolbar shortcut             header: Toolbar shortcut
-            //  entry: Toolbar shortcut      =>     removed (top-level entry)
-            //  entry: Choose button..               entry: Choose button...
             Preference preference = new Preference(requireContext());
             preference.setKey(info.key);
             preference.setTitle(info.title);
@@ -116,7 +85,7 @@
                     pref -> {
                         // For top-level entries, open the fragment itself, not MainSettings.
                         String fragmentToOpen = info.parentFragment;
-                        if (TextUtils.equals(mainSettings, info.parentFragment)) {
+                        if (TextUtils.equals(info.parentFragment, MainSettings.class.getName())) {
                             fragmentToOpen = info.fragment;
                         }
                         if (fragmentToOpen != null) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorBase.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorBase.java
index 8b5fd427..f8aba76 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorBase.java
@@ -14,7 +14,6 @@
 import org.chromium.base.ResettersForTesting;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
-import org.chromium.base.supplier.TransitiveObservableSupplier;
 import org.chromium.build.annotations.Initializer;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
@@ -29,6 +28,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Function;
 
 /** Implement methods shared across the different model implementations. */
 @NullMarked
@@ -53,8 +53,8 @@
             new TabGroupModelFilterProvider();
     private final ObservableSupplierImpl<TabModel> mTabModelSupplier =
             new ObservableSupplierImpl<>();
-    private final TransitiveObservableSupplier<TabModel, @Nullable Tab> mCurrentTabSupplier;
-    private final TransitiveObservableSupplier<TabModel, Integer> mCurrentModelTabCountSupplier;
+    private final ObservableSupplier<@Nullable Tab> mCurrentTabSupplier;
+    private final ObservableSupplier<Integer> mCurrentModelTabCountSupplier;
 
     private final ObserverList<TabModelSelectorObserver> mObservers = new ObserverList<>();
     private final ObserverList<IncognitoTabModelObserver> mIncognitoObservers =
@@ -81,11 +81,11 @@
                 };
         mTabModelSupplier.addObserver(mIncognitoReauthDialogDelegateCallback);
         mCurrentTabSupplier =
-                new TransitiveObservableSupplier<>(
-                        mTabModelSupplier, tabModel -> tabModel.getCurrentTabSupplier());
+                mTabModelSupplier.createTransitive(
+                        (Function<TabModel, ObservableSupplier<@Nullable Tab>>)
+                                TabModel::getCurrentTabSupplier);
         mCurrentModelTabCountSupplier =
-                new TransitiveObservableSupplier<>(
-                        mTabModelSupplier, tabModel -> tabModel.getTabCountSupplier());
+                mTabModelSupplier.createTransitive(TabModel::getTabCountSupplier);
     }
 
     @Initializer
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 74bd935..56e9b21 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -1184,7 +1184,7 @@
                 createTopToolbarCoordinator(
                         controlContainer,
                         buttonDataProviders,
-                        browsingModeThemeColorProvider,
+                        browsingModeThemeColorProviderWithAdjustableTint,
                         mIncognitoStateProvider,
                         initializeWithIncognitoColors,
                         mConstraintsProxy,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index 05b69d7..59170d2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -41,7 +41,6 @@
 import org.chromium.base.supplier.OneShotCallback;
 import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.OneshotSupplierImpl;
-import org.chromium.base.supplier.TransitiveObservableSupplier;
 import org.chromium.base.supplier.UnownedUserDataSupplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
@@ -342,8 +341,7 @@
     private @Nullable ScrollCaptureManager mScrollCaptureManager;
     protected final ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
     protected final ObservableSupplier<LayoutManagerImpl> mLayoutManagerImplSupplier;
-    protected final TransitiveObservableSupplier<LayoutManagerImpl, @StripVisibilityState Integer>
-            mTabStripVisibilitySupplier;
+    protected final ObservableSupplier<@StripVisibilityState Integer> mTabStripVisibilitySupplier;
     protected final ObservableSupplierImpl<LayoutManager> mLayoutManagerSupplier;
     protected final ObservableSupplier<ModalDialogManager> mModalDialogManagerSupplier;
     private final AppMenuBlocker mAppMenuBlocker;
@@ -539,9 +537,8 @@
         mLayoutManagerImplSupplier = layoutManagerSupplier;
         mLayoutManagerImplSupplier.addObserver(mLayoutManagerSupplierCallback);
         mTabStripVisibilitySupplier =
-                new TransitiveObservableSupplier<>(
-                        mLayoutManagerImplSupplier,
-                        (layoutManagerImpl) -> {
+                mLayoutManagerImplSupplier.createTransitive(
+                        layoutManagerImpl -> {
                             StripLayoutHelperManager stripLayoutHelperManager =
                                     layoutManagerImpl.getStripLayoutHelperManager();
                             return stripLayoutHelperManager != null
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java
index f441bbb3..cad2adde 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java
@@ -875,11 +875,7 @@
     @Test
     @MediumTest
     @Feature({"Android-TabSwitcher"})
-    // TODO(crbug.com/461879409): Remove disable Incognito windows when fixed
-    @DisableFeatures({
-        ChromeFeatureList.ANDROID_TAB_DECLUTTER_RESCUE_KILLSWITCH,
-        ChromeFeatureList.ANDROID_OPEN_INCOGNITO_AS_WINDOW
-    })
+    @DisableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER_RESCUE_KILLSWITCH)
     public void testIncognitoTabsNotRestoredAfterSwipe() throws Exception {
         mActivityTestRule.loadUrl(getUrl(TEST_PAGE_FILE_PATH));
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/TouchToFillCreditCardTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/TouchToFillCreditCardTest.java
index 896ae54f8..9fb501c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/TouchToFillCreditCardTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/TouchToFillCreditCardTest.java
@@ -31,7 +31,9 @@
 import org.chromium.base.FakeTimeTestRule;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.password_manager.PasswordManagerTestUtilsBridge;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -116,8 +118,10 @@
         imeAdapter.setInputMethodManagerWrapper(mInputMethodWrapper);
     }
 
+    // TODO(crbug.com/462636368): Turn on the flag after blink bug is fixed.
     @Test
     @MediumTest
+    @DisableFeatures(ChromeFeatureList.AUTOFILL_ANDROID_KEYBOARD_ACCESSORY_DYNAMIC_POSITIONING)
     public void testSelectingLocalCard() throws TimeoutException {
         // Focus the field to bring up the touch to fill for credit cards.
         DOMUtils.clickNode(mWebContents, CREDIT_CARD_NUMBER_FIELD_ID);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/TouchToFillMainFlowIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/TouchToFillMainFlowIntegrationTest.java
index 7da4042..01a2f8c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/TouchToFillMainFlowIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/TouchToFillMainFlowIntegrationTest.java
@@ -23,6 +23,8 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DoNotBatch;
+import org.chromium.base.test.util.Features.DisableFeatures;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.R;
@@ -110,8 +112,10 @@
         mSigninTestRule.tearDownRule();
     }
 
+    // TODO(crbug.com/462636368): Turn on the flag after blink bug is fixed.
     @Test
     @MediumTest
+    @DisableFeatures(ChromeFeatureList.AUTOFILL_ANDROID_KEYBOARD_ACCESSORY_DYNAMIC_POSITIONING)
     public void testClickingSuggestionPopulatesForm()
             throws TimeoutException, InterruptedException {
         // Fill the password store.
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/dom_distiller/ReaderModeBottomSheetCoordinatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/dom_distiller/ReaderModeBottomSheetCoordinatorTest.java
index e31b95a..ac0e7c6 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/dom_distiller/ReaderModeBottomSheetCoordinatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/dom_distiller/ReaderModeBottomSheetCoordinatorTest.java
@@ -112,4 +112,15 @@
         verify(mThemeColorProvider).removeThemeColorObserver(mThemeColorObserverCaptor.getValue());
         verify(mThemeColorProvider).removeTintObserver(mThemeTintObserverCaptor.getValue());
     }
+
+    @Test
+    public void testTapToExpand() {
+        when(mBottomSheetController.requestShowContent(any(), anyBoolean())).thenReturn(true);
+        when(mBottomSheetController.getSheetState())
+                .thenReturn(BottomSheetController.SheetState.PEEK);
+        mCoordinator.show(mTab);
+
+        mCoordinator.getViewForTesting().performClick();
+        verify(mBottomSheetController).expandSheet();
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/history/HistoryPaneUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/history/HistoryPaneUnitTest.java
index 18f8365..02066681 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/history/HistoryPaneUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/history/HistoryPaneUnitTest.java
@@ -20,7 +20,6 @@
 
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.hub.LoadHint;
@@ -103,7 +102,6 @@
     }
 
     @Test
-    @DisabledTest(message = "crbug.com/462509433")
     public void testNotifyLoadHint() {
         assertEquals(0, mHistoryPane.getRootView().getChildCount());
 
@@ -115,7 +113,6 @@
     }
 
     @Test
-    @DisabledTest(message = "crbug.com/462509433")
     public void testDestroy_WhileHot() {
         mHistoryPane.notifyLoadHint(LoadHint.HOT);
         mHistoryPane.destroy();
@@ -123,7 +120,6 @@
     }
 
     @Test
-    @DisabledTest(message = "crbug.com/462509433")
     public void testDestroy_WhileCold() {
         mHistoryPane.notifyLoadHint(LoadHint.HOT);
         mHistoryPane.notifyLoadHint(LoadHint.COLD);
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 a46e0c7..7f0819d 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
@@ -62,7 +62,6 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.chrome.R;
@@ -442,7 +441,6 @@
 
     @Test
     @SmallTest
-    @DisabledTest(message = "crbug.com/462509433")
     public void testSearchView() throws Exception {
         final HistoryManagerToolbar toolbar = mHistoryManager.getToolbarForTests();
         View toolbarShadow = mHistoryManager.getSelectableListLayout().getToolbarShadowForTests();
@@ -607,7 +605,6 @@
 
     @Test
     @SmallTest
-    @DisabledTest(message = "crbug.com/462509433")
     public void testSearchViewDismissedByBackPress() {
         final HistoryManagerToolbar toolbar = mHistoryManager.getToolbarForTests();
         View toolbarShadow = mHistoryManager.getSelectableListLayout().getToolbarShadowForTests();
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 87032bc..fc73d31 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -8599,6 +8599,8 @@
           Reset to default
         </message>
       </if>
+
+      <!-- Composebox -->
       <message name="IDS_NTP_COMPOSE_ENTRYPOINT" desc="The text for the compose entrypoint in the searchbox on the New Tab Page.">
         AI Mode
       </message>
@@ -8673,6 +8675,9 @@
       <message name="IDS_NTP_COMPOSE_ADD_CONTEXT" desc="The text to display to ask user to add context to their search e.g. pdf or image.">
         Add a tab and more
       </message>
+      <message name="IDS_NTP_COMPOSE_ADD_CONTEXTS" desc="The text to display to ask user to add context to their search.">
+        Add tabs and more
+      </message>
       <message name="IDS_NTP_COMPOSE_ADD_CONTEXT_TITLE" desc="The a11y title for a button that allows users to add context to their search e.g. pdf or image.">
         Add a tab or file to your search, and also use a tool
       </message>
@@ -8688,12 +8693,6 @@
       <message name="IDS_NTP_COMPOSE_DEEP_SEARCH" desc="The label for the deep search button in the context menu in the composebox as well as the chip that indicates that the user is in deep search mode.">
         Deep Search
       </message>
-      <message name="IDS_NTP_COMPOSE_ASK_ABOUT_THIS_TAB" desc="The text on a button that suggests the user can ask a question about the content of their most recent tab."  translateable="false">
-        Ask Google about previous tab
-      </message>
-      <message name="IDS_NTP_COMPOSE_ASK_ABOUT_THIS_TAB_ARIA_LABEL" desc="The aria-label for the button that suggests the user can ask a question about the content of their most recent tab. The $1 is a placeholder for the tab title." translateable="false">
-        Ask Google about previous tab: <ph name="TAB_TITLE">$1</ph>
-      </message>
       <message name="IDS_NTP_COMPOSE_CREATE_IMAGES" desc="The label for the create images button in the context menu in the composebox as well as the chip that indicates that the user is in create images mode.">
         Create images
       </message>
@@ -8733,6 +8732,18 @@
       <message name="IDS_COMPOSE_REMOVE_TOOL_CHIP_A11Y_LABEL" desc="A11y text for removing a tool in the composebox.">
         Remove <ph name="TOOL_NAME">$1<ex>Deep Search</ex></ph>
       </message>
+      <message name="IDS_COMPOSE_ASK_ABOUT_THIS_TAB" desc="Label for a button in the composebox that allows the user to ask a question about the content of their most recent tab.">
+        Ask Google about previous tab
+      </message>
+      <message name="IDS_COMPOSE_ASK_ABOUT_THIS_TAB_ARIA_LABEL" desc="The aria-label for the button that allows the user to ask a question about the content of their most recent tab. The $1 is a placeholder for the tab title.">
+        Ask Google about previous tab: <ph name="TAB_TITLE">$1</ph>
+      </message>
+      <message name="IDS_COMPOSE_ASK_ABOUT_THIS_PAGE" desc="Label for a button in the composebox that allows the user to ask a question about the content of their most recent page." >
+        Ask Google about previous page
+      </message>
+      <message name="IDS_COMPOSE_ASK_ABOUT_THIS_PAGE_ARIA_LABEL" desc="The aria-label for the button that allows the user to ask a question about the content of their most recent tab. The $1 is a placeholder for the page title.">
+        Ask Google about previous page: <ph name="PAGE_TITLE">$1</ph>
+      </message>
 
       <!-- NTP Modules -->
       <message name="IDS_NTP_MODULES_SETUP_LIST_DISABLE_TOAST_MESSAGE" desc="Text shown in the toast confirming a module has been disabled.">
@@ -8756,6 +8767,9 @@
       <message name="IDS_NTP_MODULES_DISABLE_TOAST_MESSAGE" desc="Text shown in the toast confirming a module has been disabled.">
         You won't see <ph name="MODULE_NAME">$1<ex>shopping suggestions</ex></ph> on this page again
       </message>
+      <message name="IDS_NTP_MODULES_INACTIVITY_REMOVAL" desc="Text shown in a toast informing not recently used cards have been removed." >
+        Cards removed because you haven't used them recently
+      </message>
       <message name="IDS_NTP_MODULES_HISTORY_CLUSTERS_DISABLE_TOAST_MESSAGE" desc="Text shown in the toast confirming the NTP History Clusters module has been disabled.">
         You won't see <ph name="MODULE_NAME">$1<ex>this type of card</ex></ph> again
       </message>
@@ -9097,6 +9111,11 @@
         Scan QR Code
       </message>
 
+      <!-- NTP Shortcuts -->
+      <message name="IDS_NTP_MOST_VISITED_SHORTCUTS_INACTIVITY_REMOVAL" desc="Text shown in a toast informing not recently used most visited shortcuts have been removed." >
+        Shortcuts for your most visited sites were removed because you haven't used them recently
+      </message>
+
       <!-- Extensions NTP Middle Slot Promo -->
       <message name="IDS_EXTENSIONS_PROMO_PERFORMANCE">
         Web browsing should be fast. Take a moment to <ph name="BEGIN_LINK">&lt;a href="chrome://extensions"&gt;</ph>check your extensions<ph name="END_LINK">&lt;/a&gt;</ph> now.
@@ -19193,11 +19212,6 @@
       Protected Audiences Debugging is enabled.
     </message>
 
-    <!-- Infobar shown when test-third-party-cookies switch is enabled -->
-    <message name="IDS_TEST_THIRD_PARTY_COOKIE_BLOCKING_PHASEOUT_INFO" desc="Text of infobar that shows up when the user starts Chrome with the --test-third-party-cookie-phaseout command line flag">
-      You have enabled testing third-party cookie phaseout. This cannot be overridden by the settings page. If you want to re-enable third-party cookies, relaunch Chrome with this feature disabled.
-    </message>
-
     <!-- Responsive Toolbar -->
     <message name="IDS_OVERFLOW_MENU_ITEM_TEXT_PROFILE" desc="Overflow menu item text of profile button">
       Profile
diff --git a/chrome/app/generated_resources_grd/IDS_COMPOSE_ASK_ABOUT_THIS_PAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_COMPOSE_ASK_ABOUT_THIS_PAGE.png.sha1
new file mode 100644
index 0000000..2982b71
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_COMPOSE_ASK_ABOUT_THIS_PAGE.png.sha1
@@ -0,0 +1 @@
+53b12717fbd73b84fbbdf679b2b3d28c1fa4bf5c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_COMPOSE_ASK_ABOUT_THIS_PAGE_ARIA_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_COMPOSE_ASK_ABOUT_THIS_PAGE_ARIA_LABEL.png.sha1
new file mode 100644
index 0000000..2982b71
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_COMPOSE_ASK_ABOUT_THIS_PAGE_ARIA_LABEL.png.sha1
@@ -0,0 +1 @@
+53b12717fbd73b84fbbdf679b2b3d28c1fa4bf5c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_COMPOSE_ASK_ABOUT_THIS_TAB.png.sha1 b/chrome/app/generated_resources_grd/IDS_COMPOSE_ASK_ABOUT_THIS_TAB.png.sha1
new file mode 100644
index 0000000..13f8e72
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_COMPOSE_ASK_ABOUT_THIS_TAB.png.sha1
@@ -0,0 +1 @@
+73b3fb7b05cedf1815c652e9bffd1bd9737b1330
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_COMPOSE_ASK_ABOUT_THIS_TAB_ARIA_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_COMPOSE_ASK_ABOUT_THIS_TAB_ARIA_LABEL.png.sha1
new file mode 100644
index 0000000..13f8e72
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_COMPOSE_ASK_ABOUT_THIS_TAB_ARIA_LABEL.png.sha1
@@ -0,0 +1 @@
+73b3fb7b05cedf1815c652e9bffd1bd9737b1330
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_COMPOSE_ADD_CONTEXTS.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_COMPOSE_ADD_CONTEXTS.png.sha1
new file mode 100644
index 0000000..ee53e8b
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_COMPOSE_ADD_CONTEXTS.png.sha1
@@ -0,0 +1 @@
+35b7fbf615c83e538f42e9d669a90fb4fc3ebcb9
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_INACTIVITY_REMOVAL.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_INACTIVITY_REMOVAL.png.sha1
new file mode 100644
index 0000000..784cc1c0
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_INACTIVITY_REMOVAL.png.sha1
@@ -0,0 +1 @@
+fe3ef5f74c3238da440d8f8bb6f59be5b9e81293
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MOST_VISITED_SHORTCUTS_INACTIVITY_REMOVAL.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MOST_VISITED_SHORTCUTS_INACTIVITY_REMOVAL.png.sha1
new file mode 100644
index 0000000..d241a0b
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MOST_VISITED_SHORTCUTS_INACTIVITY_REMOVAL.png.sha1
@@ -0,0 +1 @@
+9a67ab03fae877436b9136a13c88c8e03d3d33d8
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TEST_THIRD_PARTY_COOKIE_BLOCKING_PHASEOUT_INFO.png.sha1 b/chrome/app/generated_resources_grd/IDS_TEST_THIRD_PARTY_COOKIE_BLOCKING_PHASEOUT_INFO.png.sha1
deleted file mode 100644
index d47b5ffc..0000000
--- a/chrome/app/generated_resources_grd/IDS_TEST_THIRD_PARTY_COOKIE_BLOCKING_PHASEOUT_INFO.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-26d7e2077d0416e19c41a61cfe3bf10bd5788b70
\ No newline at end of file
diff --git a/chrome/app_shim/BUILD.gn b/chrome/app_shim/BUILD.gn
index 7f185d9..70ba163 100644
--- a/chrome/app_shim/BUILD.gn
+++ b/chrome/app_shim/BUILD.gn
@@ -102,3 +102,17 @@
     "Foundation.framework",
   ]
 }
+
+source_set("browser_tests") {
+  testonly = true
+  sources = [ "app_shim_controller_browsertest.mm" ]
+  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+  deps = [
+    "//chrome/app_shim",
+    "//chrome/browser/ui",
+    "//chrome/browser/web_applications:web_applications_test_support",
+    "//chrome/test:test_support",
+    "//components/remote_cocoa/app_shim",
+    "//content/test:test_support",
+  ]
+}
diff --git a/chrome/app_shim/DEPS b/chrome/app_shim/DEPS
index 771ae5d..2f704bd 100644
--- a/chrome/app_shim/DEPS
+++ b/chrome/app_shim/DEPS
@@ -12,3 +12,10 @@
   "+content/public/browser",
   "+mojo/core/embedder",
 ]
+
+specific_include_rules = {
+  r'.*browsertest.*': [
+    "+chrome/browser",
+    "+components/webapps",
+  ]
+}
diff --git a/chrome/app_shim/app_shim_controller.h b/chrome/app_shim/app_shim_controller.h
index 0e2421c..1593a18 100644
--- a/chrome/app_shim/app_shim_controller.h
+++ b/chrome/app_shim/app_shim_controller.h
@@ -44,6 +44,14 @@
     : public chrome::mojom::AppShim,
       public mac_notifications::mojom::MacNotificationProvider {
  public:
+  class TestDelegate {
+   public:
+    virtual ~TestDelegate() = default;
+    virtual void PopulateChromeCommandLine(base::CommandLine& command_line) = 0;
+  };
+
+  static void SetDelegateForTesting(TestDelegate* delegate);
+
   struct Params {
     Params();
     Params(const Params& other);
diff --git a/chrome/app_shim/app_shim_controller.mm b/chrome/app_shim/app_shim_controller.mm
index 5efeb73b..52e1030d 100644
--- a/chrome/app_shim/app_shim_controller.mm
+++ b/chrome/app_shim/app_shim_controller.mm
@@ -171,8 +171,14 @@
   raw_ptr<base::WaitableEvent> operation_finished_;
 };
 
+AppShimController::TestDelegate* g_test_delegate = nullptr;
+
 }  // namespace
 
+void AppShimController::SetDelegateForTesting(TestDelegate* delegate) {
+  g_test_delegate = delegate;
+}
+
 AppShimController::Params::Params() = default;
 AppShimController::Params::Params(const Params& other) = default;
 AppShimController::Params::~Params() = default;
@@ -418,6 +424,10 @@
     browser_command_line.AppendSwitch(switches::kNoStartupWindow);
   }
 
+  if (g_test_delegate) {
+    g_test_delegate->PopulateChromeCommandLine(browser_command_line);
+  }
+
   base::mac::LaunchApplication(
       chrome_bundle_path, browser_command_line, /*url_specs=*/{},
       {.create_new_instance = true,
@@ -514,7 +524,7 @@
   {
     mojo::PlatformChannelEndpoint endpoint;
     NSString* browser_bundle_id =
-        base::apple::ObjCCast<NSString>([NSBundle.mainBundle
+        base::apple::ObjCCast<NSString>([base::apple::MainBundle()
             objectForInfoDictionaryKey:app_mode::kBrowserBundleIDKey]);
     CHECK(browser_bundle_id);
     const std::string server_name = base::StringPrintf(
@@ -962,6 +972,7 @@
 
 bool AppShimController::WebAppIsAdHocSigned() const {
   NSNumber* isAdHocSigned =
-      NSBundle.mainBundle.infoDictionary[app_mode::kCrAppModeIsAdHocSignedKey];
+      base::apple::MainBundle()
+          .infoDictionary[app_mode::kCrAppModeIsAdHocSignedKey];
   return isAdHocSigned.boolValue;
 }
diff --git a/chrome/app_shim/app_shim_controller_browsertest.mm b/chrome/app_shim/app_shim_controller_browsertest.mm
new file mode 100644
index 0000000..52aae8d0
--- /dev/null
+++ b/chrome/app_shim/app_shim_controller_browsertest.mm
@@ -0,0 +1,245 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/app_shim/app_shim_controller.h"
+
+#include <inttypes.h>
+#include <stdint.h>
+
+#include "base/apple/bundle_locations.h"
+#include "base/at_exit.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/test/bind.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/test_timeouts.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
+#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
+#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
+#include "chrome/browser/web_applications/web_app.h"
+#include "chrome/browser/web_applications/web_app_filter.h"
+#include "chrome/browser/web_applications/web_app_helpers.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/browser/web_applications/web_app_registrar.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/mac/app_mode_common.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/launchservices_utils_mac.h"
+#include "chrome/test/base/test_launcher_utils.h"
+#include "components/remote_cocoa/app_shim/application_bridge.h"
+#include "components/remote_cocoa/app_shim/browser_native_widget_window_mac.h"
+#include "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
+#include "components/webapps/common/web_app_id.h"
+#include "content/public/test/browser_test.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/multiprocess_func_list.h"
+#include "url/gurl.h"
+
+extern "C" {
+int APP_SHIM_ENTRY_POINT_NAME(const app_mode::ChromeAppModeInfo* info);
+}
+
+#if !defined(OFFICIAL_BUILD)
+// This test relies on disabling some code signing checks to work. That is safe
+// since we only allow that in unsigned builds, but to be extra secure, the
+// relevant code is also only included in unofficial builds. Since most bots
+// test with such builds, this still gives us most of the desired test coverage.
+
+namespace web_app {
+namespace {
+constexpr std::string_view kAppShimPathSwitch = "app-shim-path";
+constexpr std::string_view kWebAppIdSwitch = "web-app-id";
+constexpr std::string_view kShimLogFileName = "shim.log";
+}  // namespace
+
+using AppId = webapps::AppId;
+
+// AppShimController does not live in the browser process, rather it lives in
+// an app shim process. Because of this, a regular browser test can not really
+// be used to test its implementation. Instead what this browser test does is:
+//   - In SetUpOnMainThread (which runs in the browser process), install a test
+//     PWA, as setup for the actual test.
+//   - Use the "multi process test" support to launch a secondary test process
+//     which will act as the app shim process. The actual test body lives in
+//     that process.
+//   - And currently, the only test specifically tests what happens when Chrome
+//     isn't already running. To do this, the test is triggered from SetUp()
+//     after InProcessBrowserTest::SetUp() returns. At this point the "test"
+//     browser has terminated, allowing us to test this behavior.
+class AppShimControllerBrowserTest : public InProcessBrowserTest {
+ public:
+  AppShimControllerBrowserTest() = default;
+  ~AppShimControllerBrowserTest() override = default;
+
+  void SetUpOnMainThread() override {
+    InProcessBrowserTest::SetUpOnMainThread();
+    // Install test PWA, and record the app id and path of generated app shim.
+    ASSERT_TRUE(embedded_test_server()->Start());
+    const GURL app_url = embedded_test_server()->GetURL("/web_apps/basic.html");
+    auto web_app_info =
+        WebAppInstallInfo::CreateWithStartUrlForTesting(app_url);
+    web_app_info->user_display_mode =
+        web_app::mojom::UserDisplayMode::kStandalone;
+    app_id_ = web_app::test::InstallWebApp(browser()->profile(),
+                                           std::move(web_app_info));
+    auto os_integration = OsIntegrationTestOverrideImpl::Get();
+    app_shim_path_ = os_integration->GetShortcutPath(
+        browser()->profile(), os_integration->chrome_apps_folder(), app_id_,
+        "");
+    ASSERT_TRUE(!app_shim_path_.empty());
+  }
+
+  void SetUp() override {
+    InProcessBrowserTest::SetUp();
+
+    // Now chrome shouldn't be running anymore, so we can do the "real" test.
+    base::TimeTicks start_time = base::TimeTicks::Now();
+
+    base::FilePath user_data_dir =
+        base::PathService::CheckedGet(chrome::DIR_USER_DATA);
+
+    base::CommandLine command_line(
+        base::GetMultiProcessTestChildBaseCommandLine());
+    // We need to make sure the child process doesn't initialize NSApp, since we
+    // want the app shim code to be able to do that.
+    command_line.AppendSwitch(switches::kDoNotCreateNSAppForTests);
+    // Pass along various other bits of information the shim process needs.
+    command_line.AppendSwitchPath(switches::kUserDataDir, user_data_dir);
+    command_line.AppendSwitchPath(kAppShimPathSwitch, app_shim_path_);
+    command_line.AppendSwitchASCII(kWebAppIdSwitch, app_id_);
+
+    // Spawn app shim process, and wait for it to exit.
+    base::Process test_child_process = base::SpawnMultiProcessTestChild(
+        "AppShimControllerBrowserTestAppShimMain", command_line,
+        base::LaunchOptions());
+    int rv = -1;
+    EXPECT_TRUE(base::WaitForMultiprocessTestChildExit(
+        test_child_process, TestTimeouts::action_max_timeout(), &rv));
+    EXPECT_EQ(0, rv);
+
+    // To validate that the app shim process did what we expected it to do, it
+    // writes a log of its actions to disk. Read that file and verify we had
+    // the epxected behavior.
+    base::FilePath log_file = user_data_dir.AppendASCII(kShimLogFileName);
+    std::string log_string;
+    base::ReadFileToString(log_file, &log_string);
+    std::vector<std::string> log = base::SplitString(
+        log_string, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+    EXPECT_THAT(log,
+                testing::ElementsAre(
+                    "Shim Started", "Window Created: BrowserNativeWidgetWindow",
+                    "Window Created: NativeWidgetMacOverlayNSWindow"));
+
+    // If the test failed, it can be hard to debug why without getting output
+    // from the Chromium process that was launched by the test. So gather that
+    // output from the system log, and include it here.
+    if (testing::Test::HasFailure()) {
+      base::TimeDelta log_time = base::TimeTicks::Now() - start_time;
+      std::vector<std::string> log_argv = {
+          "log",
+          "show",
+          "--process",
+          "Chromium",
+          "--last",
+          base::StringPrintf("%" PRId64 "s", log_time.InSeconds() + 1)};
+      std::string log_output;
+      base::GetAppOutputAndError(log_argv, &log_output);
+      LOG(INFO)
+          << "System logs during this test run (could include other tests):\n"
+          << log_output;
+    }
+  }
+
+ protected:
+  OsIntegrationTestOverrideBlockingRegistration faked_os_integration_;
+  AppId app_id_;
+  base::FilePath app_shim_path_;
+};
+
+IN_PROC_BROWSER_TEST_F(AppShimControllerBrowserTest, LaunchChrome) {
+  // Test body is in SetUp and below in app shim main.
+}
+
+class AppShimControllerDelegate : public AppShimController::TestDelegate {
+ public:
+  void PopulateChromeCommandLine(base::CommandLine& command_line) override {
+    test_launcher_utils::PrepareBrowserCommandLineForTests(&command_line);
+    command_line.AppendSwitch(switches::kAllowAppShimSignatureMismatchForTests);
+  }
+};
+
+MULTIPROCESS_TEST_MAIN(AppShimControllerBrowserTestAppShimMain) {
+  base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
+  base::FilePath user_data_dir =
+      command_line.GetSwitchValuePath(switches::kUserDataDir);
+  // Shim code expects user data dir to contain 3 extra path components that are
+  // subsequentially stripped off again.
+  std::string user_data_dir_string = user_data_dir.AppendASCII("profile")
+                                         .AppendASCII("Web Applications")
+                                         .AppendASCII("appid")
+                                         .value();
+  std::string app_id = command_line.GetSwitchValueASCII(kWebAppIdSwitch);
+  std::string app_shim_path =
+      command_line.GetSwitchValueASCII(kAppShimPathSwitch);
+  base::apple::SetOverrideMainBundlePath(base::FilePath(app_shim_path));
+  std::string framework_path = base::apple::FrameworkBundlePath().value();
+  std::string chrome_path = ::test::GuessAppBundlePath().value();
+
+  AppShimControllerDelegate controller_delegate;
+  AppShimController::SetDelegateForTesting(&controller_delegate);
+
+  // Need to reset a bunch of things before we can run the app shim
+  // initialization code.
+  base::FeatureList::ClearInstanceForTesting();
+  base::CommandLine::Reset();
+  base::AtExitManager::AllowShadowingForTesting();
+
+  // Log some data to a log file, so the main test process can validate that the
+  // test passed.
+  base::File log_file(user_data_dir.AppendASCII(kShimLogFileName),
+                      base::File::FLAG_WRITE | base::File::FLAG_CREATE);
+  auto log = [&log_file](const std::string& s) {
+    log_file.WriteAtCurrentPosAndCheck(base::as_byte_span(s + '\n'));
+    log_file.Flush();
+  };
+
+  log("Shim Started");
+
+  // Close a browser window when it gets created. This should cause chrome and
+  // the shim to terminate, marking the end of the test.
+  remote_cocoa::ApplicationBridge::Get()->SetNSWindowCreatedCallbackForTesting(
+      base::BindLambdaForTesting([&](NativeWidgetMacNSWindow* window) {
+        log(base::StringPrintf("Window Created: %s",
+                               base::SysNSStringToUTF8([window className])));
+        if ([window isKindOfClass:[BrowserNativeWidgetWindow class]]) {
+          [window performClose:nil];
+        }
+      }));
+
+  app_mode::ChromeAppModeInfo info;
+  char* argv[] = {};
+  info.argc = 0;
+  info.argv = argv;
+  info.chrome_framework_path = framework_path.c_str();
+  info.chrome_outer_bundle_path = chrome_path.c_str();
+  info.app_mode_bundle_path = app_shim_path.c_str();
+  info.app_mode_id = app_id.c_str();
+  info.app_mode_name = "Basic test app";
+  info.app_mode_url = "";
+  info.profile_dir = "";
+  info.user_data_dir = user_data_dir_string.c_str();
+  APP_SHIM_ENTRY_POINT_NAME(&info);
+
+  return 0;
+}
+
+}  // namespace web_app
+
+#endif  // !defined(OFFICIAL_BUILD)
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 0b2aaa3..f9fe50e 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -325,6 +325,8 @@
     "component_updater/optimization_guide_on_device_model_installer.h",
     "component_updater/pki_metadata_component_installer.cc",
     "component_updater/pki_metadata_component_installer.h",
+    "component_updater/pki_metadata_component_installer_policy.cc",
+    "component_updater/pki_metadata_component_installer_policy.h",
     "component_updater/pnacl_component_installer.cc",
     "component_updater/pnacl_component_installer.h",
     "component_updater/privacy_sandbox_attestations_component_installer.cc",
@@ -8340,6 +8342,13 @@
     configs += [ "//build/config/linux/nss" ]
   }
 
+  if (chrome_root_store_supported) {
+    sources += [
+      "component_updater/pki_metadata_fastpush_component_installer_policy.cc",
+      "component_updater/pki_metadata_fastpush_component_installer_policy.h",
+    ]
+  }
+
   if (chrome_root_store_cert_management_ui) {
     sources += [
       "net/server_certificate_database_service_factory.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index aba2676..f8fd6263 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -125,7 +125,6 @@
 #include "components/lens/lens_features.h"
 #include "components/manta/features.h"
 #include "components/mirroring/service/mirroring_features.h"
-#include "components/network_session_configurator/common/network_features.h"
 #include "components/network_session_configurator/common/network_switches.h"
 #include "components/no_state_prefetch/browser/no_state_prefetch_field_trial.h"
 #include "components/ntp_tiles/features.h"
@@ -1772,7 +1771,7 @@
 };
 
 const FeatureEntry::FeatureParam kNtpNextShowDeepDiveSuggestions[] = {
-    {"NtpNextShowDeepDiveSuggestionsParam", "false"},
+    {"NtpNextShowDeepDiveSuggestionsParam", "true"},
 };
 
 const FeatureEntry::FeatureVariation kNtpNextVariations[] = {
@@ -7535,6 +7534,10 @@
      FEATURE_WITH_PARAMS_VALUE_TYPE(features::kTabstripComboButton,
                                     kTabstripComboButtonVariations,
                                     "TabstripComboButton")},
+
+    {"tab-groups-focusing", flag_descriptions::kTabGroupsFocusingName,
+     flag_descriptions::kTabGroupsFocusingDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(features::kTabGroupsFocusing)},
 #endif
 
 #if !BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/actor/BUILD.gn b/chrome/browser/actor/BUILD.gn
index 6b5861c..b58dca8 100644
--- a/chrome/browser/actor/BUILD.gn
+++ b/chrome/browser/actor/BUILD.gn
@@ -118,8 +118,6 @@
     "tools/navigate_tool.h",
     "tools/navigate_tool_request.cc",
     "tools/observation_delay_controller.cc",
-    "tools/observation_delay_metrics.cc",
-    "tools/observation_delay_metrics.h",
     "tools/page_target_util.cc",
     "tools/page_target_util.h",
     "tools/page_tool.cc",
@@ -314,8 +312,6 @@
   ]
   if (enable_glic) {
     sources = [
-      "tools/observation_delay_test_util.cc",
-      "tools/observation_delay_test_util.h",
       "tools/page_stability_test_util.cc",
       "tools/page_stability_test_util.h",
       "tools/tools_test_util.cc",
@@ -372,7 +368,6 @@
       "tools/mouse_move_tool_browsertest.cc",
       "tools/navigate_tool_browsertest.cc",
       "tools/observation_delay_controller_browsertest.cc",
-      "tools/observation_delay_metrics_browsertest.cc",
       "tools/page_stability_browsertest.cc",
       "tools/page_stability_metrics_browsertest.cc",
       "tools/page_tool_browsertest.cc",
diff --git a/chrome/browser/actor/actor_policy_checker_browsertest.cc b/chrome/browser/actor/actor_policy_checker_browsertest.cc
index 64897b9..ddf5702 100644
--- a/chrome/browser/actor/actor_policy_checker_browsertest.cc
+++ b/chrome/browser/actor/actor_policy_checker_browsertest.cc
@@ -53,8 +53,10 @@
 };
 
 constexpr TestAccount kNonEnterpriseAccount = {"foo@testbar.com", ""};
+#if !BUILDFLAG(IS_CHROMEOS)
 constexpr TestAccount kEnterpriseAccount = {"foo@testenterprise.com",
                                             "testenterprise.com"};
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 }  // namespace
 
 class ActorPolicyCheckerBrowserTestBase : public ActorToolsTest {
@@ -445,14 +447,10 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-// TODO(crbug.com/460824365): Enable on ChromeOS.
-#if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE_CapabilityUpdatedForAccount DISABLED_CapabilityUpdatedForAccount
-#else
-#define MAYBE_CapabilityUpdatedForAccount CapabilityUpdatedForAccount
-#endif
+// Note: sign-out from enterprise account is not allowed in ChromeOS.
+#if !BUILDFLAG(IS_CHROMEOS)
 IN_PROC_BROWSER_TEST_F(ActorPolicyCheckerBrowserTestWithManagedAccount,
-                       MAYBE_CapabilityUpdatedForAccount) {
+                       CapabilityUpdatedForAccount) {
   // No account is signed in, thus no capability.
   EXPECT_FALSE(ActorKeyedService::Get(browser()->profile())
                    ->GetPolicyChecker()
@@ -476,6 +474,7 @@
                   ->GetPolicyChecker()
                   .can_act_on_web());
 }
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 
 IN_PROC_BROWSER_TEST_F(ActorPolicyCheckerBrowserTestWithManagedAccount,
                        GlicUserStatusChanged) {
@@ -502,15 +501,11 @@
 using ActorPolicyCheckerBrowserTestWithManagedAccountWithPolicy =
     ActorPolicyCheckerBrowserTestManagedBrowser;
 
-// TODO(crbug.com/460824365): Enable on ChromeOS.
-#if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE_CapabilityUpdatedForAccount DISABLED_CapabilityUpdatedForAccount
-#else
-#define MAYBE_CapabilityUpdatedForAccount CapabilityUpdatedForAccount
-#endif
+// Note: sign-out from enterprise account is not allowed in ChromeOS.
+#if !BUILDFLAG(IS_CHROMEOS)
 IN_PROC_BROWSER_TEST_F(
     ActorPolicyCheckerBrowserTestWithManagedAccountWithPolicy,
-    MAYBE_CapabilityUpdatedForAccount) {
+    CapabilityUpdatedForAccount) {
   UpdateGeminiActOnWebPolicy(
       glic::prefs::GlicActuationOnWebPolicyState::kDisabled);
   EXPECT_FALSE(ActorKeyedService::Get(browser()->profile())
@@ -530,5 +525,6 @@
                   ->GetPolicyChecker()
                   .can_act_on_web());
 }
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 
 }  // namespace actor
diff --git a/chrome/browser/actor/tools/observation_delay_controller.cc b/chrome/browser/actor/tools/observation_delay_controller.cc
index 6835796..dd1afb1 100644
--- a/chrome/browser/actor/tools/observation_delay_controller.cc
+++ b/chrome/browser/actor/tools/observation_delay_controller.cc
@@ -4,8 +4,6 @@
 
 #include "chrome/browser/actor/tools/observation_delay_controller.h"
 
-#include <memory>
-
 #include "base/check.h"
 #include "base/check_op.h"
 #include "base/feature_list.h"
@@ -18,7 +16,6 @@
 #include "chrome/browser/actor/actor_features.h"
 #include "chrome/browser/actor/aggregated_journal.h"
 #include "chrome/browser/actor/execution_engine.h"
-#include "chrome/browser/actor/tools/observation_delay_metrics.h"
 #include "chrome/browser/actor/tools/tool_callbacks.h"
 #include "chrome/common/actor.mojom-data-view.h"
 #include "chrome/common/actor/journal_details_builder.h"
@@ -104,9 +101,6 @@
                                       ReadyCallback callback) {
   ready_callback_ = std::move(callback);
 
-  metrics_ = std::make_unique<ObservationDelayMetrics>();
-  metrics_->Start();
-
   WebContentsObserver::Observe(target_tab.GetContents());
 
   wait_journal_entry_ = journal_->CreatePendingAsyncEntry(
@@ -122,17 +116,6 @@
   }
 }
 
-void ObservationDelayController::OnPageStable() {
-  if (state_ != State::kWaitForPageStability) {
-    return;
-  }
-
-  CHECK(metrics_);
-  metrics_->OnPageStable();
-
-  MoveToState(State::kWaitForLoadCompletion);
-}
-
 void ObservationDelayController::OnMonitorDisconnected() {
   page_stability_monitor_remote_.reset();
 
@@ -152,9 +135,6 @@
     return;
   }
 
-  CHECK(metrics_);
-  metrics_->WillMoveToState(new_state);
-
   DCheckStateTransition(state_, new_state);
 
   inner_journal_entry_.reset();
@@ -174,8 +154,7 @@
       // Unretained since `this` owns the pipe.
       page_stability_monitor_remote_->NotifyWhenStable(
           page_stability_start_delay_,
-          base::BindOnce(&ObservationDelayController::OnPageStable,
-                         base::Unretained(this)));
+          MoveToStateClosure(State::kWaitForLoadCompletion));
       break;
     }
     case State::kPageStabilityMonitorDisconnected: {
@@ -206,6 +185,12 @@
       inner_journal_entry_ = journal_->CreatePendingAsyncEntry(
           GURL::EmptyGURL(), task_id_, MakeBrowserTrackUUID(task_id_),
           "WaitForVisualStateUpdate", {});
+      // Adapt since InsertVisualStateCallback takes a bool-taking callback.
+      auto callback =
+          base::BindOnce([](base::OnceClosure post_move_to_done,
+                            bool) { std::move(post_move_to_done).Run(); },
+                         PostMoveToStateClosure(State::kMaybeDelayForLcp));
+
       if (base::FeatureList::IsEnabled(
               actor::kGlicSkipAwaitVisualStateForNewTabs) &&
           web_contents()->GetVisibility() != content::Visibility::VISIBLE &&
@@ -219,15 +204,12 @@
             web_contents()->GetLastCommittedURL(), task_id_,
             "ObservationDelay: Skip visual state update of non-captured tab",
             {});
-
-        // Posted so that this state transition is consistently async.
-        PostMoveToStateClosure(State::kMaybeDelayForLcp).Run();
+        std::move(callback).Run(true);
       } else {
         // TODO(crbug.com/414662842): This should probably ensure an update from
         // all/selected OOPIFS?
         web_contents()->GetPrimaryMainFrame()->InsertVisualStateCallback(
-            base::BindOnce(&ObservationDelayController::OnVisualStateUpdated,
-                           weak_ptr_factory_.GetWeakPtr()));
+            std::move(callback));
       }
       break;
     }
@@ -235,8 +217,9 @@
       inner_journal_entry_ = journal_->CreatePendingAsyncEntry(
           GURL::EmptyGURL(), task_id_, MakeBrowserTrackUUID(task_id_),
           "MaybeDelayForLcp", {});
-      State next_state = State::kDone;
-      if (GetLcpDelay().is_positive()) {
+      base::TimeDelta delay;
+      const base::TimeDelta lcp_delay = GetLcpDelay();
+      if (!lcp_delay.is_zero()) {
         // Conservatively, only apply delay if we get a clear signal that LCP
         // has not yet occurred on a trackable webpage. This avoids adding
         // unnecessary delays on pages where LCP is not applicable or
@@ -251,17 +234,13 @@
                 delegate->GetLargestContentfulPaintHandler()
                     .MergeMainFrameAndSubframes();
             if (!lcp.ContainsValidTime()) {
-              next_state = State::kDelayForLcp;
+              delay = lcp_delay;
             }
           }
         }
       }
       // Posted so that this state transition is consistently async.
-      PostMoveToStateClosure(next_state).Run();
-      break;
-    }
-    case State::kDelayForLcp: {
-      PostMoveToStateClosure(State::kDone, GetLcpDelay()).Run();
+      PostMoveToStateClosure(State::kDone, delay).Run();
       break;
     }
     case State::kDidTimeout: {
@@ -285,18 +264,6 @@
   return o << ObservationDelayController::StateToString(state);
 }
 
-void ObservationDelayController::OnVisualStateUpdated(bool) {
-  if (state_ != State::kWaitForVisualStateUpdate) {
-    return;
-  }
-
-  CHECK(metrics_);
-  metrics_->OnVisualStateUpdated();
-
-  // Posted so that this state transition is consistently async.
-  PostMoveToStateClosure(State::kMaybeDelayForLcp).Run();
-}
-
 void ObservationDelayController::DCheckStateTransition(State old_state,
                                                        State new_state) {
 #if DCHECK_IS_ON()
@@ -320,10 +287,6 @@
                State::kMaybeDelayForLcp}},
           {State::kMaybeDelayForLcp,
               {State::kDidTimeout,
-               State::kDelayForLcp,
-               State::kDone}},
-           {State::kDelayForLcp,
-              {State::kDidTimeout,
                State::kDone}},
           {State::kDidTimeout,
               {State::kDone}}
@@ -338,9 +301,6 @@
     return;
   }
 
-  CHECK(metrics_);
-  metrics_->OnLoadCompleted();
-
   MoveToState(State::kWaitForVisualStateUpdate);
 }
 
@@ -361,9 +321,7 @@
     case State::kWaitForVisualStateUpdate:
       return "WaitForVisualStateUpdate";
     case State::kMaybeDelayForLcp:
-      return "MaybeDelayForLcp";
-    case State::kDelayForLcp:
-      return "DelayForLcp";
+      return "WaitForLcp";
     case State::kDidTimeout:
       return "DidTimeout";
     case State::kDone:
diff --git a/chrome/browser/actor/tools/observation_delay_controller.h b/chrome/browser/actor/tools/observation_delay_controller.h
index 6232412d..f25854e 100644
--- a/chrome/browser/actor/tools/observation_delay_controller.h
+++ b/chrome/browser/actor/tools/observation_delay_controller.h
@@ -5,7 +5,6 @@
 #ifndef CHROME_BROWSER_ACTOR_TOOLS_OBSERVATION_DELAY_CONTROLLER_H_
 #define CHROME_BROWSER_ACTOR_TOOLS_OBSERVATION_DELAY_CONTROLLER_H_
 
-#include <memory>
 #include <ostream>
 #include <string_view>
 
@@ -27,8 +26,6 @@
 
 namespace actor {
 
-class ObservationDelayMetrics;
-
 // Observes a page during tool-use and determines when the page has settled
 // after an action and is ready for for an observation.
 //
@@ -80,7 +77,6 @@
     kWaitForLoadCompletion,
     kWaitForVisualStateUpdate,
     kMaybeDelayForLcp,
-    kDelayForLcp,
     kDidTimeout,
     kDone
   };
@@ -97,8 +93,6 @@
       std::ostream& o,
       const ObservationDelayController::State& state);
 
-  void OnPageStable();
-  void OnVisualStateUpdated(bool);
   void OnMonitorDisconnected();
   void DCheckStateTransition(State old_state, State new_state);
   void MoveToState(State state);
@@ -120,8 +114,6 @@
   std::unique_ptr<AggregatedJournal::PendingAsyncEntry> inner_journal_entry_;
   base::TimeDelta page_stability_start_delay_;
 
-  std::unique_ptr<ObservationDelayMetrics> metrics_;
-
   base::WeakPtrFactory<ObservationDelayController> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/actor/tools/observation_delay_controller_browsertest.cc b/chrome/browser/actor/tools/observation_delay_controller_browsertest.cc
index bd7410c..6a4e10b 100644
--- a/chrome/browser/actor/tools/observation_delay_controller_browsertest.cc
+++ b/chrome/browser/actor/tools/observation_delay_controller_browsertest.cc
@@ -4,15 +4,23 @@
 
 #include "chrome/browser/actor/tools/observation_delay_controller.h"
 
+#include <optional>
 #include <string>
+#include <string_view>
+#include <utility>
 
+#include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
+#include "base/test/test_timeouts.h"
 #include "base/test/with_feature_override.h"
 #include "base/time/time.h"
-#include "chrome/browser/actor/tools/observation_delay_test_util.h"
+#include "chrome/browser/actor/actor_keyed_service.h"
+#include "chrome/browser/actor/aggregated_journal.h"
+#include "chrome/browser/actor/tools/page_stability_test_util.h"
 #include "chrome/common/actor/task_id.h"
 #include "chrome/common/chrome_features.h"
+#include "chrome/test/base/chrome_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
 #include "components/tabs/public/tab_interface.h"
@@ -20,6 +28,7 @@
 #include "content/public/test/back_forward_cache_util.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
+#include "net/test/embedded_test_server/controllable_http_response.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/strings/str_format.h"
 #include "url/gurl.h"
@@ -38,7 +47,198 @@
 
 using State = ::actor::ObservationDelayController::State;
 
-class ObservationDelayControllerTest : public ObservationDelayTest {
+std::string_view ToString(State state) {
+  return ObservationDelayController::StateToString(state);
+}
+
+// Helper to start a navigation in the main frame to a page that reaches
+// DOMContentLoaded in the main frame but doesn't reach the load event until
+// `RunToLoadEvent` is called. It does this by deferring a subframe navigation.
+class NavigateToLoadDeferredPage {
+ public:
+  NavigateToLoadDeferredPage(WebContents* web_contents,
+                             net::EmbeddedTestServer* server)
+      : url_main_frame_(server->GetURL("/actor/simple_iframe.html")),
+        url_subframe_(server->GetURL("/actor/blank.html")),
+        web_contents_(web_contents) {
+    subframe_manager_ =
+        std::make_unique<TestNavigationManager>(web_contents, url_subframe_);
+    main_manager_ =
+        std::make_unique<TestNavigationManager>(web_contents, url_main_frame_);
+  }
+
+  [[nodiscard]] bool RunToDOMContentLoadedEvent() {
+    // Now start a navigation to a new document that has an iframe. Block the
+    // iframe's navigation to control the load event timing.
+    bool begin_navigate =
+        BeginNavigateToURLFromRenderer(web_contents_, url_main_frame_);
+    EXPECT_TRUE(begin_navigate);
+    if (!begin_navigate) {
+      return false;
+    }
+
+    // Wait for the main frame navigation to finish and for the main document to
+    // reach DOMContentLoaded and for a frame to be presented but prevent the
+    // subframe from completing.
+    bool wait_for_main_finished = main_manager_->WaitForNavigationFinished();
+    EXPECT_TRUE(wait_for_main_finished);
+    if (!wait_for_main_finished) {
+      return false;
+    }
+
+    bool wait_for_dom_content_loaded =
+        WaitForDOMContentLoaded(web_contents_->GetPrimaryMainFrame());
+    EXPECT_TRUE(wait_for_dom_content_loaded);
+    if (!wait_for_dom_content_loaded) {
+      return false;
+    }
+
+    WaitForCopyableViewInWebContents(web_contents_);
+    bool wait_for_subframe_response = subframe_manager_->WaitForResponse();
+    EXPECT_TRUE(wait_for_subframe_response);
+    return wait_for_subframe_response;
+  }
+
+  [[nodiscard]] bool RunToLoadEvent() {
+    return subframe_manager_->WaitForNavigationFinished();
+  }
+
+ private:
+  const GURL url_main_frame_;
+  const GURL url_subframe_;
+  WebContents* web_contents_;
+
+  std::unique_ptr<TestNavigationManager> subframe_manager_;
+  std::unique_ptr<TestNavigationManager> main_manager_;
+};
+
+class TestObservationDelayController : public ObservationDelayController {
+ public:
+  TestObservationDelayController(RenderFrameHost& target_frame,
+                                 TaskId task_id,
+                                 AggregatedJournal& journal,
+                                 PageStabilityConfig page_stability_config)
+      : ObservationDelayController(target_frame,
+                                   task_id,
+                                   journal,
+                                   page_stability_config) {
+    // Ensure the monitor is created in the renderer before returning. This
+    // ensures the PageStabilityMonitor captures the initial state at the
+    // current point in the test.
+    page_stability_monitor_remote_.FlushForTesting();
+  }
+  ~TestObservationDelayController() override = default;
+
+  [[nodiscard]] bool WaitForState(State state) {
+    if (state_ == state) {
+      return true;
+    }
+
+    base::RunLoop run_loop;
+    waiting_state_ = state;
+    quit_closure_ = run_loop.QuitClosure();
+    run_loop.Run();
+    waiting_state_.reset();
+    return state_ == state;
+  }
+  State GetState() const { return state_; }
+
+ protected:
+  void SetState(State state) override {
+    ObservationDelayController::SetState(state);
+    if (!waiting_state_) {
+      return;
+    }
+
+    if (*waiting_state_ == state) {
+      std::move(quit_closure_).Run();
+    } else if (state == State::kDone) {
+      ADD_FAILURE()
+          << "ObservationDelayController completed without reaching waited on "
+             "value: "
+          << ToString(*waiting_state_);
+      std::move(quit_closure_).Run();
+    }
+  }
+
+  std::optional<State> waiting_state_;
+  base::OnceClosure quit_closure_;
+};
+
+class ObservationDelayControllerTestBase : public PageStabilityTest {
+ public:
+  ObservationDelayControllerTestBase() = default;
+  ObservationDelayControllerTestBase(
+      const ObservationDelayControllerTestBase&) = delete;
+  ObservationDelayControllerTestBase& operator=(
+      const ObservationDelayControllerTestBase&) = delete;
+
+  ~ObservationDelayControllerTestBase() override = default;
+
+  // Sleep long enough to verify that a state we're in is steady. This is, of
+  // course, non difinitive but in practice should shake out any cases where the
+  // state isn't steady. Scales the tiny timeout for more certainty.
+  void SteadyStateSleep() {
+    base::TimeDelta timeout = TestTimeouts::tiny_timeout() * 3;
+    base::RunLoop run_loop;
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+        FROM_HERE, run_loop.QuitClosure(), timeout);
+    run_loop.Run();
+  }
+
+  TabInterface* active_tab() { return chrome_test_utils::GetActiveTab(this); }
+
+  actor::AggregatedJournal& journal() { return journal_; }
+
+  ObservationDelayController::PageStabilityConfig PageStabilityConfig() const {
+    // Use default values.
+    return ObservationDelayController::PageStabilityConfig();
+  }
+
+  [[nodiscard]] bool InitiateFetchRequest() {
+    // Perform a same-document navigation. The page has a navigation handler
+    // that will initiate a fetch from this event.  This works via the
+    // navigation handler on the harness' test page.
+    CHECK_EQ(web_contents()->GetURL(), GetPageStabilityTestURL());
+    CHECK_EQ(GetOutputText(), "INITIAL");
+
+    const GURL hash_navigation_to_initiate_fetch =
+        embedded_test_server()->GetURL("/actor/page_stability.html#fetch");
+
+    bool navigate_result = content::NavigateToURL(
+        web_contents(), hash_navigation_to_initiate_fetch);
+    EXPECT_TRUE(navigate_result);
+    if (!navigate_result) {
+      return false;
+    }
+
+    fetch_response().WaitForRequest();
+    // The page should not receive a response until `Respond` is called.
+    EXPECT_EQ(GetOutputText(), "INITIAL");
+    return true;
+  }
+
+  [[nodiscard]] bool DoesReachSteadyState(
+      TestObservationDelayController& controller,
+      State state) {
+    if (!controller.WaitForState(state)) {
+      return false;
+    }
+
+    // Ensure the controller stays there for some time.
+    SteadyStateSleep();
+    EXPECT_EQ(ToString(controller.GetState()), ToString(state));
+    return controller.GetState() == state;
+  }
+
+ private:
+  actor::AggregatedJournal journal_;
+  std::unique_ptr<actor::AggregatedJournal::PendingAsyncEntry> journal_entry_;
+  std::unique_ptr<ObservationDelayController> controller_;
+};
+
+class ObservationDelayControllerTest
+    : public ObservationDelayControllerTestBase {
  public:
   ObservationDelayControllerTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
@@ -211,7 +411,8 @@
   EXPECT_TRUE(DoesReachSteadyState(controller, State::kWaitForPageStability));
 }
 
-class ObservationDelayControllerLcpTest : public ObservationDelayTest {
+class ObservationDelayControllerLcpTest
+    : public ObservationDelayControllerTestBase {
  public:
   static constexpr int kLcpDelayInMs = 3000;
   ObservationDelayControllerLcpTest() {
@@ -277,7 +478,6 @@
   controller.Wait(*active_tab(), result.GetCallback());
 
   ASSERT_TRUE(controller.WaitForState(State::kMaybeDelayForLcp));
-  ASSERT_TRUE(controller.WaitForState(State::kDelayForLcp));
   ASSERT_TRUE(result.Wait());
 
   // The total time should be at least the LCP delay, because the empty page
diff --git a/chrome/browser/actor/tools/observation_delay_metrics.cc b/chrome/browser/actor/tools/observation_delay_metrics.cc
deleted file mode 100644
index 7369336..0000000
--- a/chrome/browser/actor/tools/observation_delay_metrics.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/actor/tools/observation_delay_metrics.h"
-
-#include "base/check.h"
-#include "base/metrics/histogram_functions.h"
-#include "base/notreached.h"
-#include "base/time/time.h"
-#include "chrome/browser/actor/tools/observation_delay_controller.h"
-
-namespace actor {
-
-const char kActorObservationDelayStateDurationWaitForPageStabilityMetricName[] =
-    "Actor.ObservationDelay.StateDuration.WaitForPageStability";
-
-const char
-    kActorObservationDelayStateDurationWaitForLoadCompletionMetricName[] =
-        "Actor.ObservationDelay.StateDuration.WaitForLoadCompletion";
-
-const char
-    kActorObservationDelayStateDurationWaitForVisualStateUpdateMetricName[] =
-        "Actor.ObservationDelay.StateDuration.WaitForVisualStateUpdate";
-
-const char kActorObservationDelayTotalWaitDurationMetricName[] =
-    "Actor.ObservationDelay.TotalWaitDuration";
-
-const char kActorObservationDelayDidTimeoutMetricName[] =
-    "Actor.ObservationDelay.DidTimeout";
-
-const char kActorObservationDelayLcpDelayNeededMetricName[] =
-    "Actor.ObservationDelay.LcpDelayNeeded";
-
-ObservationDelayMetrics::ObservationDelayMetrics() = default;
-
-ObservationDelayMetrics::~ObservationDelayMetrics() = default;
-
-void ObservationDelayMetrics::Start() {
-  wait_start_time_ = base::TimeTicks::Now();
-}
-
-void ObservationDelayMetrics::WillMoveToState(
-    ObservationDelayController::State state) {
-  base::TimeTicks now = base::TimeTicks::Now();
-
-  switch (state) {
-    case ObservationDelayController::State::kInitial:
-      NOTREACHED();
-    case ObservationDelayController::State::kWaitForPageStability:
-      wait_for_page_stability_.start_time = now;
-      break;
-    case ObservationDelayController::State::kPageStabilityMonitorDisconnected:
-      break;
-    case ObservationDelayController::State::kWaitForLoadCompletion:
-      wait_for_load_completion_.start_time = now;
-      break;
-    case ObservationDelayController::State::kWaitForVisualStateUpdate:
-      wait_for_visual_state_update_.start_time = now;
-      break;
-    case ObservationDelayController::State::kMaybeDelayForLcp:
-      delay_for_lcp_ = false;
-      break;
-    case ObservationDelayController::State::kDelayForLcp:
-      delay_for_lcp_ = true;
-      break;
-    case ObservationDelayController::State::kDidTimeout:
-      did_timeout = true;
-      break;
-    case ObservationDelayController::State::kDone:
-      base::UmaHistogramBoolean(kActorObservationDelayDidTimeoutMetricName,
-                                did_timeout);
-
-      if (!did_timeout) {
-        CHECK(!wait_start_time_.is_null());
-        base::UmaHistogramTimes(
-            kActorObservationDelayTotalWaitDurationMetricName,
-            base::TimeTicks::Now() - wait_start_time_);
-
-        if (wait_for_page_stability_.IsValid()) {
-          base::UmaHistogramTimes(
-              kActorObservationDelayStateDurationWaitForPageStabilityMetricName,
-              wait_for_page_stability_.end_time -
-                  wait_for_page_stability_.start_time);
-        }
-        if (wait_for_load_completion_.IsValid()) {
-          base::UmaHistogramTimes(
-              kActorObservationDelayStateDurationWaitForLoadCompletionMetricName,
-              wait_for_load_completion_.end_time -
-                  wait_for_load_completion_.start_time);
-        }
-        if (wait_for_visual_state_update_.IsValid()) {
-          base::UmaHistogramTimes(
-              kActorObservationDelayStateDurationWaitForVisualStateUpdateMetricName,
-              wait_for_visual_state_update_.end_time -
-                  wait_for_visual_state_update_.start_time);
-        }
-
-        if (delay_for_lcp_.has_value()) {
-          base::UmaHistogramBoolean(
-              kActorObservationDelayLcpDelayNeededMetricName, *delay_for_lcp_);
-        }
-      }
-      break;
-  }
-}
-
-void ObservationDelayMetrics::OnPageStable() {
-  wait_for_page_stability_.end_time = base::TimeTicks::Now();
-  CHECK(wait_for_page_stability_.IsValid());
-}
-
-void ObservationDelayMetrics::OnLoadCompleted() {
-  wait_for_load_completion_.end_time = base::TimeTicks::Now();
-  CHECK(wait_for_load_completion_.IsValid());
-}
-
-void ObservationDelayMetrics::OnVisualStateUpdated() {
-  wait_for_visual_state_update_.end_time = base::TimeTicks::Now();
-  CHECK(wait_for_visual_state_update_.IsValid());
-}
-
-}  // namespace actor
diff --git a/chrome/browser/actor/tools/observation_delay_metrics.h b/chrome/browser/actor/tools/observation_delay_metrics.h
deleted file mode 100644
index dad71411..0000000
--- a/chrome/browser/actor/tools/observation_delay_metrics.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_ACTOR_TOOLS_OBSERVATION_DELAY_METRICS_H_
-#define CHROME_BROWSER_ACTOR_TOOLS_OBSERVATION_DELAY_METRICS_H_
-
-#include <optional>
-
-#include "base/time/time.h"
-#include "chrome/browser/actor/tools/observation_delay_controller.h"
-
-namespace actor {
-
-extern const char
-    kActorObservationDelayStateDurationWaitForPageStabilityMetricName[];
-extern const char
-    kActorObservationDelayStateDurationWaitForLoadCompletionMetricName[];
-extern const char
-    kActorObservationDelayStateDurationWaitForVisualStateUpdateMetricName[];
-extern const char kActorObservationDelayTotalWaitDurationMetricName[];
-extern const char kActorObservationDelayDidTimeoutMetricName[];
-extern const char kActorObservationDelayLcpDelayNeededMetricName[];
-
-class ObservationDelayMetrics {
- public:
-  ObservationDelayMetrics();
-  ~ObservationDelayMetrics();
-
-  void Start();
-
-  void WillMoveToState(ObservationDelayController::State state);
-
-  void OnPageStable();
-
-  void OnLoadCompleted();
-
-  void OnVisualStateUpdated();
-
- private:
-  struct StateDuration {
-    bool IsValid() const {
-      return !start_time.is_null() && !end_time.is_null();
-    }
-    base::TimeTicks start_time;
-    base::TimeTicks end_time;
-  };
-
-  void RecordStateDuration(ObservationDelayController::State state);
-
-  // The time at which it starts to wait for page stability/page
-  // loading.
-  base::TimeTicks wait_start_time_;
-
-  // The duration waiting for page stability.
-  StateDuration wait_for_page_stability_;
-
-  // The duration waiting for page loading.
-  StateDuration wait_for_load_completion_;
-
-  // The duration waiting for visual state update.
-  StateDuration wait_for_visual_state_update_;
-
-  // Whether the observation delay completed due to timeout.
-  bool did_timeout = false;
-
-  // Whether additional delay is applied to wait for LCP. Will be `std::nullopt`
-  // until `kMaybeDelayForLCP` state is entered.
-  std::optional<bool> delay_for_lcp_;
-};
-
-}  // namespace actor
-
-#endif  // CHROME_BROWSER_ACTOR_TOOLS_OBSERVATION_DELAY_METRICS_H_
diff --git a/chrome/browser/actor/tools/observation_delay_metrics_browsertest.cc b/chrome/browser/actor/tools/observation_delay_metrics_browsertest.cc
deleted file mode 100644
index 5e651e5..0000000
--- a/chrome/browser/actor/tools/observation_delay_metrics_browsertest.cc
+++ /dev/null
@@ -1,253 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/actor/tools/observation_delay_metrics.h"
-
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
-#include "base/test/test_future.h"
-#include "chrome/browser/actor/tools/observation_delay_controller.h"
-#include "chrome/browser/actor/tools/observation_delay_test_util.h"
-#include "chrome/common/actor/task_id.h"
-#include "chrome/common/chrome_features.h"
-#include "content/public/test/browser_test.h"
-#include "content/public/test/browser_test_utils.h"
-#include "url/gurl.h"
-
-namespace actor {
-namespace {
-
-using ::base::test::TestFuture;
-
-using State = ::actor::ObservationDelayController::State;
-
-class ObservationDelayMetricsTest : public ObservationDelayTest {
- public:
-  ObservationDelayMetricsTest() {
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        features::kGlicActor,
-        {// Disable LCP delay
-         {features::kActorObservationDelayLcp.name, "0ms"}});
-  }
-  ObservationDelayMetricsTest(const ObservationDelayMetricsTest&) = delete;
-  ObservationDelayMetricsTest& operator=(const ObservationDelayMetricsTest&) =
-      delete;
-  ~ObservationDelayMetricsTest() override = default;
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_F(ObservationDelayMetricsTest, CompleteWithoutLoading) {
-  base::HistogramTester histogram_tester;
-
-  const GURL url = embedded_test_server()->GetURL("/title1.html");
-  ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
-
-  TestObservationDelayController controller(*main_frame(), actor::TaskId(),
-                                            journal(), PageStabilityConfig());
-
-  TestFuture<void> result;
-  controller.Wait(*active_tab(), result.GetCallback());
-
-  ASSERT_TRUE(result.Wait());
-
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayTotalWaitDurationMetricName, 1);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayStateDurationWaitForPageStabilityMetricName, 1);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayStateDurationWaitForLoadCompletionMetricName, 0);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayStateDurationWaitForVisualStateUpdateMetricName, 1);
-  histogram_tester.ExpectUniqueSample(
-      kActorObservationDelayDidTimeoutMetricName,
-      /*sample=*/false, 1);
-  histogram_tester.ExpectUniqueSample(
-      kActorObservationDelayLcpDelayNeededMetricName,
-      /*sample=*/false, 1);
-}
-
-IN_PROC_BROWSER_TEST_F(ObservationDelayMetricsTest, CompleteWithLoading) {
-  base::HistogramTester histogram_tester;
-
-  ASSERT_TRUE(
-      content::NavigateToURL(web_contents(), GetPageStabilityTestURL()));
-
-  TestObservationDelayController controller(*main_frame(), actor::TaskId(),
-                                            journal(), PageStabilityConfig());
-  ASSERT_TRUE(InitiateFetchRequest());
-
-  TestFuture<void> result;
-  controller.Wait(*active_tab(), result.GetCallback());
-  ASSERT_TRUE(DoesReachSteadyState(controller, State::kWaitForPageStability));
-
-  const GURL url = embedded_test_server()->GetURL("/actor/blank.html");
-  content::TestNavigationManager manager(web_contents(), url);
-
-  ASSERT_TRUE(content::BeginNavigateToURLFromRenderer(web_contents(), url));
-
-  // Stop before committing the navigation. The observer should remain waiting
-  // on page stability.
-  ASSERT_TRUE(manager.WaitForResponse());
-  ASSERT_TRUE(DoesReachSteadyState(controller, State::kWaitForPageStability));
-
-  // Complete the navigation. The controller should wait for load.
-  ASSERT_TRUE(manager.WaitForNavigationFinished());
-
-  ASSERT_TRUE(result.Wait());
-
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayTotalWaitDurationMetricName, 1);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayStateDurationWaitForPageStabilityMetricName, 1);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayStateDurationWaitForLoadCompletionMetricName, 1);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayStateDurationWaitForVisualStateUpdateMetricName, 1);
-  histogram_tester.ExpectUniqueSample(
-      kActorObservationDelayDidTimeoutMetricName,
-      /*sample=*/false, 1);
-  histogram_tester.ExpectUniqueSample(
-      kActorObservationDelayLcpDelayNeededMetricName,
-      /*sample=*/false, 1);
-}
-
-class ObservationDelayMetricsTimeoutTest : public ObservationDelayTest {
- public:
-  ObservationDelayMetricsTimeoutTest() {
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        features::kGlicActor,
-        {{features::kActorObservationDelayTimeout.name, "3s"}});
-  }
-  ObservationDelayMetricsTimeoutTest(
-      const ObservationDelayMetricsTimeoutTest&) = delete;
-  ObservationDelayMetricsTimeoutTest& operator=(
-      const ObservationDelayMetricsTimeoutTest&) = delete;
-  ~ObservationDelayMetricsTimeoutTest() override = default;
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_F(ObservationDelayMetricsTimeoutTest,
-                       TimeoutOnPageStability) {
-  base::HistogramTester histogram_tester;
-
-  ASSERT_TRUE(
-      content::NavigateToURL(web_contents(), GetPageStabilityTestURL()));
-
-  TestObservationDelayController controller(*main_frame(), actor::TaskId(),
-                                            journal(), PageStabilityConfig());
-  ASSERT_TRUE(InitiateFetchRequest());
-
-  TestFuture<void> result;
-  controller.Wait(*active_tab(), result.GetCallback());
-
-  ASSERT_TRUE(result.Wait());
-
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayTotalWaitDurationMetricName, 0);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayStateDurationWaitForPageStabilityMetricName, 0);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayStateDurationWaitForLoadCompletionMetricName, 0);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayStateDurationWaitForVisualStateUpdateMetricName, 0);
-  histogram_tester.ExpectUniqueSample(
-      kActorObservationDelayDidTimeoutMetricName,
-      /*sample=*/true, 1);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayLcpDelayNeededMetricName, 0);
-}
-
-IN_PROC_BROWSER_TEST_F(ObservationDelayMetricsTimeoutTest,
-                       TimeoutOnLoadCompletion) {
-  base::HistogramTester histogram_tester;
-
-  ASSERT_TRUE(
-      content::NavigateToURL(web_contents(), GetPageStabilityTestURL()));
-
-  TestObservationDelayController controller(*main_frame(), actor::TaskId(),
-                                            journal(), PageStabilityConfig());
-
-  ASSERT_TRUE(InitiateFetchRequest());
-
-  // Start waiting, since a fetch is in progress we should be waiting for page
-  // stability.
-  TestFuture<void> result;
-  controller.Wait(*active_tab(), result.GetCallback());
-
-  ASSERT_TRUE(DoesReachSteadyState(controller, State::kWaitForPageStability));
-  EXPECT_FALSE(result.IsReady());
-
-  // Start a navigation to a page that finishes navigating but is deferred on
-  // the load event.
-  NavigateToLoadDeferredPage deferred_navigation(web_contents(),
-                                                 embedded_test_server());
-  ASSERT_TRUE(deferred_navigation.RunToDOMContentLoadedEvent());
-
-  // The controller should reach the loading state and stay there.
-  ASSERT_TRUE(DoesReachSteadyState(controller, State::kWaitForLoadCompletion));
-  EXPECT_FALSE(result.IsReady());
-
-  ASSERT_TRUE(result.Wait());
-
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayTotalWaitDurationMetricName, 0);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayStateDurationWaitForPageStabilityMetricName, 0);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayStateDurationWaitForLoadCompletionMetricName, 0);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayStateDurationWaitForVisualStateUpdateMetricName, 0);
-  histogram_tester.ExpectUniqueSample(
-      kActorObservationDelayDidTimeoutMetricName,
-      /*sample=*/true, 1);
-  histogram_tester.ExpectTotalCount(
-      kActorObservationDelayLcpDelayNeededMetricName, 0);
-}
-
-class ObservationDelayMetricsLcpDelayTest : public ObservationDelayTest {
- public:
-  ObservationDelayMetricsLcpDelayTest() {
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        features::kGlicActor,
-        {{features::kActorObservationDelayLcp.name, "100ms"}});
-  }
-  ObservationDelayMetricsLcpDelayTest(
-      const ObservationDelayMetricsLcpDelayTest&) = delete;
-  ObservationDelayMetricsLcpDelayTest& operator=(
-      const ObservationDelayMetricsLcpDelayTest&) = delete;
-  ~ObservationDelayMetricsLcpDelayTest() override = default;
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_F(ObservationDelayMetricsLcpDelayTest, LcpDelayNeeded) {
-  base::HistogramTester histogram_tester;
-
-  // Navigate to an empty html page. This is a standard navigation, so the
-  // PageLoadMetrics system will run, but no LCP will ever be recorded
-  // because there is no content.
-  const GURL url = embedded_test_server()->GetURL("/actor/blank.html");
-  ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
-
-  TestObservationDelayController controller(*main_frame(), actor::TaskId(),
-                                            journal(), PageStabilityConfig());
-
-  TestFuture<void> result;
-  controller.Wait(*active_tab(), result.GetCallback());
-
-  ASSERT_TRUE(controller.WaitForState(State::kDelayForLcp));
-  ASSERT_TRUE(result.Wait());
-
-  histogram_tester.ExpectUniqueSample(
-      kActorObservationDelayLcpDelayNeededMetricName,
-      /*sample=*/true, 1);
-}
-
-}  // namespace
-}  // namespace actor
diff --git a/chrome/browser/actor/tools/observation_delay_test_util.cc b/chrome/browser/actor/tools/observation_delay_test_util.cc
deleted file mode 100644
index 4c8a277..0000000
--- a/chrome/browser/actor/tools/observation_delay_test_util.cc
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/actor/tools/observation_delay_test_util.h"
-
-#include <memory>
-#include <string_view>
-#include <utility>
-
-#include "base/check_op.h"
-#include "base/run_loop.h"
-#include "base/task/single_thread_task_runner.h"
-#include "base/test/test_timeouts.h"
-#include "base/time/time.h"
-#include "chrome/browser/actor/tools/observation_delay_controller.h"
-#include "chrome/test/base/chrome_test_utils.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/browser_test_utils.h"
-#include "net/test/embedded_test_server/controllable_http_response.h"
-#include "net/test/embedded_test_server/embedded_test_server.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "url/gurl.h"
-
-namespace actor {
-
-namespace {
-
-using State = ::actor::ObservationDelayController::State;
-
-std::string_view ToString(State state) {
-  return ObservationDelayController::StateToString(state);
-}
-
-}  // namespace
-
-NavigateToLoadDeferredPage::NavigateToLoadDeferredPage(
-    content::WebContents* web_contents,
-    net::test_server::EmbeddedTestServer* server)
-    : url_main_frame_(server->GetURL("/actor/simple_iframe.html")),
-      url_subframe_(server->GetURL("/actor/blank.html")),
-      web_contents_(web_contents) {
-  subframe_manager_ = std::make_unique<content::TestNavigationManager>(
-      web_contents, url_subframe_);
-  main_manager_ = std::make_unique<content::TestNavigationManager>(
-      web_contents, url_main_frame_);
-}
-
-NavigateToLoadDeferredPage::~NavigateToLoadDeferredPage() = default;
-
-bool NavigateToLoadDeferredPage::RunToDOMContentLoadedEvent() {
-  // Now start a navigation to a new document that has an iframe. Block the
-  // iframe's navigation to control the load event timing.
-  bool begin_navigate =
-      content::BeginNavigateToURLFromRenderer(web_contents_, url_main_frame_);
-  EXPECT_TRUE(begin_navigate);
-  if (!begin_navigate) {
-    return false;
-  }
-
-  // Wait for the main frame navigation to finish and for the main document to
-  // reach DOMContentLoaded and for a frame to be presented but prevent the
-  // subframe from completing.
-  bool wait_for_main_finished = main_manager_->WaitForNavigationFinished();
-  EXPECT_TRUE(wait_for_main_finished);
-  if (!wait_for_main_finished) {
-    return false;
-  }
-
-  bool wait_for_dom_content_loaded =
-      WaitForDOMContentLoaded(web_contents_->GetPrimaryMainFrame());
-  EXPECT_TRUE(wait_for_dom_content_loaded);
-  if (!wait_for_dom_content_loaded) {
-    return false;
-  }
-
-  WaitForCopyableViewInWebContents(web_contents_);
-  bool wait_for_subframe_response = subframe_manager_->WaitForResponse();
-  EXPECT_TRUE(wait_for_subframe_response);
-  return wait_for_subframe_response;
-}
-
-bool NavigateToLoadDeferredPage::RunToLoadEvent() {
-  return subframe_manager_->WaitForNavigationFinished();
-}
-
-TestObservationDelayController::TestObservationDelayController(
-    content::RenderFrameHost& target_frame,
-    TaskId task_id,
-    AggregatedJournal& journal,
-    PageStabilityConfig page_stability_config)
-    : ObservationDelayController(target_frame,
-                                 task_id,
-                                 journal,
-                                 page_stability_config) {
-  // Ensure the monitor is created in the renderer before returning. This
-  // ensures the PageStabilityMonitor captures the initial state at the
-  // current point in the test.
-  page_stability_monitor_remote_.FlushForTesting();
-}
-
-TestObservationDelayController::~TestObservationDelayController() = default;
-
-bool TestObservationDelayController::WaitForState(State state) {
-  if (state_ == state) {
-    return true;
-  }
-
-  base::RunLoop run_loop;
-  waiting_state_ = state;
-  quit_closure_ = run_loop.QuitClosure();
-  run_loop.Run();
-  waiting_state_.reset();
-  return state_ == state;
-}
-
-void TestObservationDelayController::SetState(State state) {
-  ObservationDelayController::SetState(state);
-  if (!waiting_state_) {
-    return;
-  }
-
-  if (*waiting_state_ == state) {
-    std::move(quit_closure_).Run();
-  } else if (state == State::kDone) {
-    ADD_FAILURE()
-        << "ObservationDelayController completed without reaching waited on "
-           "value: "
-        << ToString(*waiting_state_);
-    std::move(quit_closure_).Run();
-  }
-}
-
-ObservationDelayTest::ObservationDelayTest() = default;
-
-ObservationDelayTest::~ObservationDelayTest() = default;
-
-tabs::TabInterface* ObservationDelayTest::active_tab() {
-  return chrome_test_utils::GetActiveTab(this);
-}
-
-ObservationDelayController::PageStabilityConfig
-ObservationDelayTest::PageStabilityConfig() const {
-  // Use default values.
-  return ObservationDelayController::PageStabilityConfig();
-}
-
-bool ObservationDelayTest::InitiateFetchRequest() {
-  // Perform a same-document navigation. The page has a navigation handler
-  // that will initiate a fetch from this event.  This works via the
-  // navigation handler on the harness' test page.
-  CHECK_EQ(web_contents()->GetURL(), GetPageStabilityTestURL());
-  CHECK_EQ(GetOutputText(), "INITIAL");
-
-  const GURL hash_navigation_to_initiate_fetch =
-      embedded_test_server()->GetURL("/actor/page_stability.html#fetch");
-
-  bool navigate_result =
-      content::NavigateToURL(web_contents(), hash_navigation_to_initiate_fetch);
-  EXPECT_TRUE(navigate_result);
-  if (!navigate_result) {
-    return false;
-  }
-
-  fetch_response().WaitForRequest();
-  // The page should not receive a response until `Respond` is called.
-  EXPECT_EQ(GetOutputText(), "INITIAL");
-  return true;
-}
-
-// Sleep long enough to verify that a state we're in is steady. This is, of
-// course, non difinitive but in practice should shake out any cases where the
-// state isn't steady. Scales the tiny timeout for more certainty.
-void ObservationDelayTest::SteadyStateSleep() {
-  base::TimeDelta timeout = TestTimeouts::tiny_timeout() * 3;
-  base::RunLoop run_loop;
-  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
-      FROM_HERE, run_loop.QuitClosure(), timeout);
-  run_loop.Run();
-}
-
-bool ObservationDelayTest::DoesReachSteadyState(
-    TestObservationDelayController& controller,
-    State state) {
-  if (!controller.WaitForState(state)) {
-    return false;
-  }
-
-  // Ensure the controller stays there for some time.
-  SteadyStateSleep();
-  EXPECT_EQ(ToString(controller.GetState()), ToString(state));
-  return controller.GetState() == state;
-}
-
-}  // namespace actor
diff --git a/chrome/browser/actor/tools/observation_delay_test_util.h b/chrome/browser/actor/tools/observation_delay_test_util.h
deleted file mode 100644
index 7d877c3..0000000
--- a/chrome/browser/actor/tools/observation_delay_test_util.h
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_ACTOR_TOOLS_OBSERVATION_DELAY_TEST_UTIL_H_
-#define CHROME_BROWSER_ACTOR_TOOLS_OBSERVATION_DELAY_TEST_UTIL_H_
-
-#include <memory>
-#include <optional>
-
-#include "base/functional/callback.h"
-#include "base/functional/callback_forward.h"
-#include "chrome/browser/actor/aggregated_journal.h"
-#include "chrome/browser/actor/tools/observation_delay_controller.h"
-#include "chrome/browser/actor/tools/page_stability_test_util.h"
-#include "chrome/common/actor/task_id.h"
-#include "url/gurl.h"
-
-namespace content {
-class RenderFrameHost;
-class TestNavigationManager;
-class WebContents;
-}  // namespace content
-
-namespace net::test_server {
-class EmbeddedTestServer;
-}  // namespace net::test_server
-
-namespace tabs {
-class TabInterface;
-}  // namespace tabs
-
-namespace actor {
-
-// Helper to start a navigation in the main frame to a page that reaches
-// DOMContentLoaded in the main frame but doesn't reach the load event until
-// `RunToLoadEvent` is called. It does this by deferring a subframe navigation.
-class NavigateToLoadDeferredPage {
- public:
-  NavigateToLoadDeferredPage(content::WebContents* web_contents,
-                             net::test_server::EmbeddedTestServer* server);
-  ~NavigateToLoadDeferredPage();
-
-  [[nodiscard]] bool RunToDOMContentLoadedEvent();
-
-  [[nodiscard]] bool RunToLoadEvent();
-
- private:
-  const GURL url_main_frame_;
-  const GURL url_subframe_;
-  content::WebContents* web_contents_;
-
-  std::unique_ptr<content::TestNavigationManager> subframe_manager_;
-  std::unique_ptr<content::TestNavigationManager> main_manager_;
-};
-
-class TestObservationDelayController : public ObservationDelayController {
- public:
-  TestObservationDelayController(content::RenderFrameHost& target_frame,
-                                 TaskId task_id,
-                                 AggregatedJournal& journal,
-                                 PageStabilityConfig page_stability_config);
-  ~TestObservationDelayController() override;
-
-  [[nodiscard]] bool WaitForState(State state);
-  State GetState() const { return state_; }
-
- protected:
-  void SetState(State state) override;
-
-  std::optional<State> waiting_state_;
-  base::OnceClosure quit_closure_;
-};
-
-class ObservationDelayTest : public PageStabilityTest {
- public:
-  ObservationDelayTest();
-  ObservationDelayTest(const ObservationDelayTest&) = delete;
-  ObservationDelayTest& operator=(const ObservationDelayTest&) = delete;
-  ~ObservationDelayTest() override;
-
-  tabs::TabInterface* active_tab();
-
-  actor::AggregatedJournal& journal() { return journal_; }
-
-  ObservationDelayController::PageStabilityConfig PageStabilityConfig() const;
-
-  [[nodiscard]] bool InitiateFetchRequest();
-
-  [[nodiscard]] bool DoesReachSteadyState(
-      TestObservationDelayController& controller,
-      ObservationDelayController::State state);
-
- private:
-  void SteadyStateSleep();
-
-  actor::AggregatedJournal journal_;
-};
-
-}  // namespace actor
-
-#endif  // CHROME_BROWSER_ACTOR_TOOLS_OBSERVATION_DELAY_TEST_UTIL_H_
diff --git a/chrome/browser/android/compositor/scene_layer/bookmark_bar_scene_layer.cc b/chrome/browser/android/compositor/scene_layer/bookmark_bar_scene_layer.cc
index bb0528bb..827dc9e 100644
--- a/chrome/browser/android/compositor/scene_layer/bookmark_bar_scene_layer.cc
+++ b/chrome/browser/android/compositor/scene_layer/bookmark_bar_scene_layer.cc
@@ -117,9 +117,12 @@
     const base::android::JavaParamRef<jobject>& joffset_tag) {
   ui::ResourceManager* resource_manager =
       ui::ResourceManagerImpl::FromJavaObject(jresource_manager);
+  if (!resource_manager) {
+    return;
+  }
+
   ui::Resource* resource = resource_manager->GetResource(
       ui::ANDROID_RESOURCE_TYPE_DYNAMIC_BITMAP, view_resource_id);
-
   if (!resource) {
     return;
   }
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.cc b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
index 2941f50..91598932 100644
--- a/chrome/browser/android/omnibox/autocomplete_controller_android.cc
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
@@ -181,9 +181,10 @@
   input_.set_prefer_keyword(prefer_keyword);
   input_.set_allow_exact_keyword_match(allow_exact_keyword_match);
   input_.set_omit_asynchronous_matches(!want_asynchronous_matches);
-  if (composebox_query_controller_bridge_) {
-    const auto& inputs =
-        composebox_query_controller_bridge_->GetLensOverlaySuggestInputs();
+
+  auto* bridge = composebox_query_controller_bridge_.get();
+  if (bridge) {
+    const auto& inputs = bridge->GetLensOverlaySuggestInputs();
     if (AreLensSuggestInputsReady(inputs)) {
       input_.set_lens_overlay_suggest_inputs(inputs);
     }
@@ -304,9 +305,9 @@
   input_.set_focus_type(OFT::INTERACTION_FOCUS);
 
   // Apply any AI Modes and Tools.
-  if (composebox_query_controller_bridge_) {
-    const auto& inputs =
-        composebox_query_controller_bridge_->GetLensOverlaySuggestInputs();
+  auto* bridge = composebox_query_controller_bridge_.get();
+  if (bridge) {
+    const auto& inputs = bridge->GetLensOverlaySuggestInputs();
     if (AreLensSuggestInputsReady(inputs)) {
       input_.set_lens_overlay_suggest_inputs(inputs);
     }
@@ -489,9 +490,15 @@
 void AutocompleteControllerAndroid::SetComposeboxQueryControllerBridge(
     JNIEnv* env,
     uintptr_t composebox_controller_ptr) {
+  if (!composebox_controller_ptr) {
+    composebox_query_controller_bridge_.reset();
+    return;
+  }
+
   composebox_query_controller_bridge_ =
       reinterpret_cast<ComposeboxQueryControllerBridge*>(
-          composebox_controller_ptr);
+          composebox_controller_ptr)
+          ->AsWeakPtr();
   if (composebox_query_controller_bridge_) {
     composebox_query_controller_bridge_->SetLensSignalsReadyObserver(
         base::BindRepeating(&AutocompleteControllerAndroid::OnLensSignalsReady,
@@ -570,6 +577,11 @@
   // This method may only be called once the ComposeboxQueryController is
   // notified that the Lens service is done processing inputs.
   if (input_.IsZeroSuggest()) {
+    auto* bridge = composebox_query_controller_bridge_.get();
+    if (!bridge) {
+      return;
+    }
+
     // Abort pending asynchronous suggestion updates.
     if (!autocomplete_controller_->done()) {
       autocomplete_controller_->Stop(AutocompleteStopReason::kClobbered);
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.h b/chrome/browser/android/omnibox/autocomplete_controller_android.h
index f7c0642..dc462aa9 100644
--- a/chrome/browser/android/omnibox/autocomplete_controller_android.h
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.h
@@ -213,7 +213,8 @@
 
   // The ComposeBoxQueryController instance related to the same input session.
   // This may and often will be unset.
-  raw_ptr<ComposeboxQueryControllerBridge> composebox_query_controller_bridge_;
+  base::WeakPtr<ComposeboxQueryControllerBridge>
+      composebox_query_controller_bridge_;
 
   // Factory used to create asynchronously invoked callbacks.
   // Retained throughout the lifetime of the AutocompleteControllerAndroid.
diff --git a/chrome/browser/android/omnibox/composebox_query_controller_bridge.cc b/chrome/browser/android/omnibox/composebox_query_controller_bridge.cc
index e4fdd07a..be2f9e6 100644
--- a/chrome/browser/android/omnibox/composebox_query_controller_bridge.cc
+++ b/chrome/browser/android/omnibox/composebox_query_controller_bridge.cc
@@ -78,6 +78,11 @@
   delete this;
 }
 
+base::WeakPtr<ComposeboxQueryControllerBridge>
+ComposeboxQueryControllerBridge::AsWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
 void ComposeboxQueryControllerBridge::NotifySessionStarted(JNIEnv* env) {
   query_controller_->InitializeIfNeeded();
 }
diff --git a/chrome/browser/android/omnibox/composebox_query_controller_bridge.h b/chrome/browser/android/omnibox/composebox_query_controller_bridge.h
index 7b9514a6..55aa36a 100644
--- a/chrome/browser/android/omnibox/composebox_query_controller_bridge.h
+++ b/chrome/browser/android/omnibox/composebox_query_controller_bridge.h
@@ -69,6 +69,8 @@
     lens_signals_ready_callback_ = std::move(callback);
   }
 
+  base::WeakPtr<ComposeboxQueryControllerBridge> AsWeakPtr();
+
  private:
   void OnGetTabPageContext(
       JNIEnv* env,
diff --git a/chrome/browser/android/storage_loaded_data_android.cc b/chrome/browser/android/storage_loaded_data_android.cc
index 8ad5b52..af8e3f1 100644
--- a/chrome/browser/android/storage_loaded_data_android.cc
+++ b/chrome/browser/android/storage_loaded_data_android.cc
@@ -32,17 +32,9 @@
 
 using ScopedJavaLocalRef = base::android::ScopedJavaLocalRef<jobject>;
 
-base::OnceCallback<void(TabAndroid*)> WrapCallbackForJni(
-    OnTabInterfaceCreation&& callback) {
-  return base::BindOnce([](OnTabInterfaceCreation inner_cb,
-                           TabAndroid* tab) { std::move(inner_cb).Run(tab); },
-                        std::move(callback));
-}
-
 base::android::ScopedJavaLocalRef<jobject> CreateLoadedTabState(
     JNIEnv* env,
-    LoadedTabState& loaded_tab) {
-  tabs_pb::TabState& tab_state = loaded_tab.first;
+    tabs_pb::TabState& tab_state) {
   base::android::ScopedJavaLocalRef<jobject> j_web_contents_state_buffer;
   long j_web_contents_state_string_pointer = 0;
   if (tab_state.has_web_contents_state_bytes()) {
@@ -64,10 +56,6 @@
     j_tab_group_id = base::android::TokenAndroid::Create(env, tab_group_token);
   }
 
-  base::android::ScopedJavaLocalRef<jobject> j_on_tab_created_callback =
-      base::android::ToJniCallback(
-          env, WrapCallbackForJni(std::move(loaded_tab.second)));
-
   base::android::ScopedJavaLocalRef<jobject> j_tab_state =
       Java_StorageLoadedData_createTabState(
           env, tab_state.parent_id(), tab_state.root_id(),
@@ -80,13 +68,13 @@
           j_tab_group_id, tab_state.tab_has_sensitive_content(),
           tab_state.is_pinned());
 
-  return Java_StorageLoadedData_createLoadedTabState(
-      env, tab_state.tab_id(), j_tab_state, j_on_tab_created_callback);
+  return Java_StorageLoadedData_createLoadedTabState(env, tab_state.tab_id(),
+                                                     j_tab_state);
 }
 
 base::android::ScopedJavaLocalRef<jobjectArray> CreateLoadedTabStates(
     JNIEnv* env,
-    std::vector<LoadedTabState>& loaded_tabs) {
+    std::vector<tabs_pb::TabState>& loaded_tabs) {
   std::vector<base::android::ScopedJavaLocalRef<jobject>>
       j_loaded_tab_state_vector;
   for (auto& loaded_tab : loaded_tabs) {
diff --git a/chrome/browser/apps/app_shim/app_shim_manager_mac.cc b/chrome/browser/apps/app_shim/app_shim_manager_mac.cc
index 3ecb4c6..b25c417 100644
--- a/chrome/browser/apps/app_shim/app_shim_manager_mac.cc
+++ b/chrome/browser/apps/app_shim/app_shim_manager_mac.cc
@@ -66,6 +66,7 @@
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
 #include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_switches.h"
 #include "chrome/common/mac/app_mode_common.h"
 #include "chrome/services/mac_notifications/public/mojom/mac_notifications.mojom.h"
 #include "components/crash/core/common/crash_key.h"
@@ -1360,16 +1361,37 @@
 
   if (requires_adhoc_signature) {
     IsAcceptablyAdHocCodeSigned(
-        audit_token, base::BindOnce(
-                         [](base::OnceCallback<void(bool)> callback,
-                            SignatureValidationResult result) {
-                           RecordSignatureValidationResult(result);
-                           const bool is_acceptably_signed =
-                               result ==
-                               SignatureValidationResult::kSuccessAdHoc;
-                           std::move(callback).Run(is_acceptably_signed);
-                         },
-                         std::move(callback)));
+        audit_token,
+        base::BindOnce(
+            [](base::OnceCallback<void(bool)> callback,
+               SignatureValidationResult result) {
+              RecordSignatureValidationResult(result);
+              bool is_acceptably_signed =
+                  result == SignatureValidationResult::kSuccessAdHoc;
+#if !defined(OFFICIAL_BUILD)
+              if (!is_acceptably_signed &&
+                  base::CommandLine::ForCurrentProcess()->HasSwitch(
+                      switches::kAllowAppShimSignatureMismatchForTests)) {
+                // In some tests we need to allow bypassing of code
+                // signing requirements. This is safe because we
+                // only allow this if the framework bundle is not
+                // signed.
+                auto app_shim_requirement = CreateAppShimRequirement();
+                if (!app_shim_requirement.has_value() &&
+                    app_shim_requirement.error() ==
+                        apps::MissingRequirementReason::NoOrAdHocSignature) {
+                  LOG(ERROR) << "Shim is not acceptably code signed, but "
+                                "allowing anyway since this is an "
+                                "unsigned developer build and --"
+                             << switches::kAllowAppShimSignatureMismatchForTests
+                             << " was passed.";
+                  is_acceptably_signed = true;
+                }
+              }
+#endif  // !defined(OFFICIAL_BUILD)
+              std::move(callback).Run(is_acceptably_signed);
+            },
+            std::move(callback)));
     return;
   }
 
diff --git a/chrome/browser/ash/accessibility/accessibility_event_rewriter_delegate_impl.h b/chrome/browser/ash/accessibility/accessibility_event_rewriter_delegate_impl.h
index ed74c18..188fd2b 100644
--- a/chrome/browser/ash/accessibility/accessibility_event_rewriter_delegate_impl.h
+++ b/chrome/browser/ash/accessibility/accessibility_event_rewriter_delegate_impl.h
@@ -18,7 +18,6 @@
 // Passes key events from Ash's EventRewriter to accessibility component
 // extension code. Used by ChromeVox and Switch Access. Reports ChromeVox's
 // unhandled key events back to Ash for continued dispatch.
-// TODO(http://crbug.com/839541): Avoid reposting unhandled events.
 class AccessibilityEventRewriterDelegateImpl
     : public AccessibilityEventRewriterDelegate,
       public content::WebContentsDelegate {
diff --git a/chrome/browser/ash/arc/enterprise/cert_store/cert_store_service.cc b/chrome/browser/ash/arc/enterprise/cert_store/cert_store_service.cc
index 18b81e0..911d97b 100644
--- a/chrome/browser/ash/arc/enterprise/cert_store/cert_store_service.cc
+++ b/chrome/browser/ash/arc/enterprise/cert_store/cert_store_service.cc
@@ -246,11 +246,11 @@
   if (!nss_cert)
     return std::nullopt;
 
-  // TODO(b/193771095) Use a valid wincx.
+  // Passing a nullptr for wincx is a hack but not worth fixing now, see b/193771095
   // Must have a private key in order to access label and ID.
   SECKEYPrivateKey* private_key =
       PK11_FindKeyByAnyCert(nss_cert.get(), nullptr /* wincx */);
-  // TODO(b/193771180) Investigate race condition with null private keys.
+  // Potential race condition with null private keys (see b/193771180)
   if (!private_key)
     return std::nullopt;
   crypto::ScopedSECKEYPrivateKey priv_key_destroyer(private_key);
diff --git a/chrome/browser/ash/child_accounts/child_user_service.h b/chrome/browser/ash/child_accounts/child_user_service.h
index eceb96ab..2042198 100644
--- a/chrome/browser/ash/child_accounts/child_user_service.h
+++ b/chrome/browser/ash/child_accounts/child_user_service.h
@@ -38,8 +38,6 @@
 }  // namespace app_time
 
 // Facade that exposes child user related functionality on Chrome OS.
-// TODO(crbug.com/40106527): Migrate ConsumerStatusReportingService,
-// EventBasedStatusReporting and ScreenTimeController to ChildUserService.
 class ChildUserService : public KeyedService,
                          public app_time::AppTimeLimitInterface,
                          public app_time::AppActivityReportInterface {
diff --git a/chrome/browser/ash/extensions/external_cache_impl.cc b/chrome/browser/ash/extensions/external_cache_impl.cc
index 5e50e2b3..1d5428e 100644
--- a/chrome/browser/ash/extensions/external_cache_impl.cc
+++ b/chrome/browser/ash/extensions/external_cache_impl.cc
@@ -255,8 +255,6 @@
 
       // Don't try to DownloadMissingExtensions() from here,
       // since it can cause a fail/retry loop.
-      // TODO(crbug.com/40715565) trigger re-installation mechanism with
-      // exponential back-off.
       return;
     }
   }
diff --git a/chrome/browser/ash/growth/install_web_app_action_performer.cc b/chrome/browser/ash/growth/install_web_app_action_performer.cc
index a1cca554..540b488 100644
--- a/chrome/browser/ash/growth/install_web_app_action_performer.cc
+++ b/chrome/browser/ash/growth/install_web_app_action_performer.cc
@@ -88,8 +88,6 @@
     return;
   }
 
-  // TODO(b/306023057): Record an UMA metric regarding why
-  // we were not able to successfully record the app.
   std::move(callback).Run(
       growth::ActionResult::kFailure,
       growth::ActionResultReason::kWebAppInstallFailedOther);
@@ -138,8 +136,6 @@
 
   auto info = ParseInstallWebAppActionPerformerParams(params);
   if (!info) {
-    // TODO(b/306023057): Record an UMA metric that parsing the params
-    // has failed.
     std::move(callback).Run(growth::ActionResult::kFailure,
                             growth::ActionResultReason::kParsingActionFailed);
     return;
diff --git a/chrome/browser/ash/main_parts/BUILD.gn b/chrome/browser/ash/main_parts/BUILD.gn
index db22e22..99bb1baa 100644
--- a/chrome/browser/ash/main_parts/BUILD.gn
+++ b/chrome/browser/ash/main_parts/BUILD.gn
@@ -133,7 +133,6 @@
     "//chromeos/ash/components/login/session",
     "//chromeos/ash/components/mojo_service_manager:mojo_service_manager_connection",
     "//chromeos/ash/components/network",
-    "//chromeos/ash/components/network/portal_detector",
     "//chromeos/ash/components/peripheral_notification",
     "//chromeos/ash/components/power",
     "//chromeos/ash/components/report",
diff --git a/chrome/browser/ash/main_parts/chrome_browser_main_parts_ash.cc b/chrome/browser/ash/main_parts/chrome_browser_main_parts_ash.cc
index c0f7769..f6354a89 100644
--- a/chrome/browser/ash/main_parts/chrome_browser_main_parts_ash.cc
+++ b/chrome/browser/ash/main_parts/chrome_browser_main_parts_ash.cc
@@ -226,7 +226,6 @@
 #include "chromeos/ash/components/network/fast_transition_observer.h"
 #include "chromeos/ash/components/network/network_cert_loader.h"
 #include "chromeos/ash/components/network/network_handler.h"
-#include "chromeos/ash/components/network/portal_detector/network_portal_detector_stub.h"
 #include "chromeos/ash/components/network/system_token_cert_db_storage.h"
 #include "chromeos/ash/components/network/traffic_counters_handler.h"
 #include "chromeos/ash/components/pcie_peripheral/ash_usb_detector.h"
@@ -319,16 +318,6 @@
   base::SetLinuxDistro("CrOS " + version.value_or("0.0.0.0"));
 }
 
-// Creates an instance of the NetworkPortalDetector implementation or a stub.
-void InitializeNetworkPortalDetector() {
-  if (network_portal_detector::SetForTesting()) {
-    return;
-  }
-  network_portal_detector::SetNetworkPortalDetector(
-      new NetworkPortalDetectorStub());
-  network_portal_detector::GetInstance()->Enable();
-}
-
 void ApplySigninProfileModifications(Profile* profile) {
   DCHECK(ProfileHelper::IsSigninProfile(profile));
   auto* prefs = profile->GetPrefs();
@@ -1261,13 +1250,6 @@
     BootTimesRecorder::Get()->OnChromeProcessStart(
         CHECK_DEREF(g_browser_process->local_state()));
 
-    // Initialize the network portal detector for Chrome OS. The network
-    // portal detector starts to listen for notifications from
-    // NetworkStateHandler and initiates captive portal detection for
-    // active networks. Should be called before call to initialize
-    // ChromeSessionManager because it depends on NetworkPortalDetector.
-    InitializeNetworkPortalDetector();
-
     // Initialize an observer to update NetworkHandler's pref based services.
     network_pref_state_observer_ = std::make_unique<NetworkPrefStateObserver>(
         *g_browser_process->local_state());
@@ -1840,13 +1822,6 @@
   quirks_policy_controller_.reset();
   quirks::QuirksManager::Shutdown();
 
-  // Called after ChromeBrowserMainPartsLinux::PostMainMessageLoopRun() (which
-  // calls chrome::CloseAsh()) because some parts of WebUI depend on
-  // NetworkPortalDetector.
-  if (pre_profile_init_called_) {
-    network_portal_detector::Shutdown();
-  }
-
   bluetooth_log_controller_.reset();
 
   g_browser_process->platform_part()->ShutdownSessionManager();
diff --git a/chrome/browser/ash/net/BUILD.gn b/chrome/browser/ash/net/BUILD.gn
index 61ec773..df7ed73 100644
--- a/chrome/browser/ash/net/BUILD.gn
+++ b/chrome/browser/ash/net/BUILD.gn
@@ -26,8 +26,6 @@
     "delay_network_call.h",
     "dhcp_wpad_url_client.cc",
     "dhcp_wpad_url_client.h",
-    "network_portal_detector_test_impl.cc",
-    "network_portal_detector_test_impl.h",
     "network_pref_state_observer.cc",
     "network_pref_state_observer.h",
     "network_throttling_observer.cc",
@@ -71,7 +69,6 @@
     "//chromeos/ash/components/mojo_service_manager",
     "//chromeos/ash/components/nearby/common/connections_manager",
     "//chromeos/ash/components/network",
-    "//chromeos/ash/components/network/portal_detector",
     "//chromeos/ash/components/policy/policy_blocklist_service",
     "//chromeos/ash/components/sync_wifi",
     "//chromeos/ash/components/system",
@@ -107,17 +104,6 @@
   ]
 }
 
-static_library("test_support") {
-  testonly = true
-
-  sources = [
-    "network_portal_detector_test_utils.cc",
-    "network_portal_detector_test_utils.h",
-  ]
-
-  deps = [ "//base" ]
-}
-
 source_set("unit_tests") {
   testonly = true
 
@@ -132,7 +118,6 @@
 
   deps = [
     ":net",
-    ":test_support",
     "//ash/constants",
     "//base",
     "//base/test:test_support",
diff --git a/chrome/browser/ash/net/network_portal_detector_test_impl.cc b/chrome/browser/ash/net/network_portal_detector_test_impl.cc
deleted file mode 100644
index c5682e9..0000000
--- a/chrome/browser/ash/net/network_portal_detector_test_impl.cc
+++ /dev/null
@@ -1,49 +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.
-
-#include "chrome/browser/ash/net/network_portal_detector_test_impl.h"
-
-#include <memory>
-
-#include "base/functional/callback.h"
-#include "base/logging.h"
-#include "chromeos/ash/components/network/network_handler.h"
-#include "chromeos/ash/components/network/network_state.h"
-#include "chromeos/ash/components/network/network_state_handler.h"
-
-namespace ash {
-
-NetworkPortalDetectorTestImpl::NetworkPortalDetectorTestImpl() = default;
-
-NetworkPortalDetectorTestImpl::~NetworkPortalDetectorTestImpl() = default;
-
-void NetworkPortalDetectorTestImpl::SetDefaultNetworkForTesting(
-    const std::string& guid) {
-  DVLOG(1) << "SetDefaultNetworkForTesting: " << guid;
-  if (guid.empty()) {
-    default_network_.reset();
-  } else {
-    default_network_ = std::make_unique<NetworkState>("/service/" + guid);
-    default_network_->SetGuid(guid);
-  }
-}
-
-std::string NetworkPortalDetectorTestImpl::GetDefaultNetworkGuid() const {
-  if (!default_network_) {
-    return "";
-  }
-
-  return default_network_->guid();
-}
-
-bool NetworkPortalDetectorTestImpl::IsEnabled() {
-  return enabled_;
-}
-
-void NetworkPortalDetectorTestImpl::Enable() {
-  DVLOG(1) << "NetworkPortalDetectorTestImpl: Enabled.";
-  enabled_ = true;
-}
-
-}  // namespace ash
diff --git a/chrome/browser/ash/net/network_portal_detector_test_impl.h b/chrome/browser/ash/net/network_portal_detector_test_impl.h
deleted file mode 100644
index c1c3199..0000000
--- a/chrome/browser/ash/net/network_portal_detector_test_impl.h
+++ /dev/null
@@ -1,46 +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 CHROME_BROWSER_ASH_NET_NETWORK_PORTAL_DETECTOR_TEST_IMPL_H_
-#define CHROME_BROWSER_ASH_NET_NETWORK_PORTAL_DETECTOR_TEST_IMPL_H_
-
-#include <map>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/functional/callback_forward.h"
-#include "chromeos/ash/components/network/portal_detector/network_portal_detector.h"
-
-namespace ash {
-
-class NetworkState;
-
-class NetworkPortalDetectorTestImpl : public NetworkPortalDetector {
- public:
-  NetworkPortalDetectorTestImpl();
-
-  NetworkPortalDetectorTestImpl(const NetworkPortalDetectorTestImpl&) = delete;
-  NetworkPortalDetectorTestImpl& operator=(
-      const NetworkPortalDetectorTestImpl&) = delete;
-
-  ~NetworkPortalDetectorTestImpl() override;
-
-  void SetDefaultNetworkForTesting(const std::string& guid);
-
-  // Returns the GUID of the network the detector considers to be default.
-  std::string GetDefaultNetworkGuid() const;
-
-  // NetworkPortalDetector implementation:
-  bool IsEnabled() override;
-  void Enable() override;
-
- private:
-  bool enabled_ = false;
-  std::unique_ptr<NetworkState> default_network_;
-};
-
-}  // namespace ash
-
-#endif  // CHROME_BROWSER_ASH_NET_NETWORK_PORTAL_DETECTOR_TEST_IMPL_H_
diff --git a/chrome/browser/ash/net/network_portal_detector_test_utils.cc b/chrome/browser/ash/net/network_portal_detector_test_utils.cc
deleted file mode 100644
index cef447c6..0000000
--- a/chrome/browser/ash/net/network_portal_detector_test_utils.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2014 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/ash/net/network_portal_detector_test_utils.h"
-
-#include <stddef.h>
-
-#include <algorithm>
-#include <memory>
-
-#include "base/logging.h"
-#include "base/metrics/histogram_base.h"
-#include "base/metrics/histogram_samples.h"
-#include "base/metrics/statistics_recorder.h"
-
-namespace ash {
-
-EnumHistogramChecker::EnumHistogramChecker(const std::string& histogram,
-                                           int count,
-                                           base::HistogramSamples* base)
-    : histogram_(histogram), expect_(count), base_(base) {}
-
-EnumHistogramChecker::~EnumHistogramChecker() = default;
-
-EnumHistogramChecker* EnumHistogramChecker::Expect(int key, int value) {
-  expect_[key] = value;
-  return this;
-}
-
-bool EnumHistogramChecker::Check() {
-  bool empty = false;
-  size_t num_zeroes = static_cast<size_t>(std::ranges::count(expect_, 0));
-  if (num_zeroes == expect_.size())
-    empty = true;
-  base::HistogramBase* histogram =
-      base::StatisticsRecorder::FindHistogram(histogram_);
-  if (!histogram) {
-    if (!empty) {
-      LOG(ERROR) << "Non-empty expectations for " << histogram_ << " "
-                 << "which does not exists.";
-      return false;
-    }
-    return true;
-  }
-  std::unique_ptr<base::HistogramSamples> samples =
-      histogram->SnapshotSamples();
-  if (!samples.get()) {
-    if (!empty) {
-      LOG(ERROR) << "Non-empty expectations for " << histogram_ << " "
-                 << "for which samples do not exist.";
-      return false;
-    }
-    return true;
-  }
-
-  bool ok = true;
-  for (size_t i = 0; i < expect_.size(); ++i) {
-    const int base = base_ ? base_->GetCount(i) : 0;
-    const int actual = samples->GetCount(i) - base;
-    if (actual != expect_[i]) {
-      LOG(ERROR) << "Histogram: " << histogram_ << ", value #" << i << ", "
-                 << "expected: " << expect_[i] << ", actual: " << actual;
-      ok = false;
-    }
-  }
-  return ok;
-}
-
-}  // namespace ash
diff --git a/chrome/browser/ash/net/network_portal_detector_test_utils.h b/chrome/browser/ash/net/network_portal_detector_test_utils.h
deleted file mode 100644
index 417d634..0000000
--- a/chrome/browser/ash/net/network_portal_detector_test_utils.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2014 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_ASH_NET_NETWORK_PORTAL_DETECTOR_TEST_UTILS_H_
-#define CHROME_BROWSER_ASH_NET_NETWORK_PORTAL_DETECTOR_TEST_UTILS_H_
-
-#include <string>
-#include <vector>
-
-#include "base/memory/raw_ptr.h"
-
-namespace base {
-class HistogramSamples;
-}
-
-namespace ash {
-
-// Checks enum values in a histogram.
-class EnumHistogramChecker {
- public:
-  EnumHistogramChecker(const std::string& histogram, int count,
-                       base::HistogramSamples* base);
-
-  EnumHistogramChecker(const EnumHistogramChecker&) = delete;
-  EnumHistogramChecker& operator=(const EnumHistogramChecker&) = delete;
-
-  ~EnumHistogramChecker();
-
-  // Sets expectation for a given enum key. |key| must be between 0
-  // and expect_.size().
-  EnumHistogramChecker* Expect(int key, int value);
-
-  // Actually accesses histogram and checks values for all keys.
-  bool Check();
-
- private:
-  // Name of a histogram.
-  std::string histogram_;
-
-  // List of expectations.
-  std::vector<int> expect_;
-
-  // When not NULL, expected values are compared with actual values
-  // minus base.
-  raw_ptr<base::HistogramSamples> base_;
-};
-
-}  // namespace ash
-
-#endif  // CHROME_BROWSER_ASH_NET_NETWORK_PORTAL_DETECTOR_TEST_UTILS_H_
diff --git a/chrome/browser/ash/test/BUILD.gn b/chrome/browser/ash/test/BUILD.gn
index 079238d..571ee16 100644
--- a/chrome/browser/ash/test/BUILD.gn
+++ b/chrome/browser/ash/test/BUILD.gn
@@ -32,6 +32,8 @@
     "//chromeos/ash/components/policy/device_local_account",
     "//chromeos/ash/components/settings",
     "//chromeos/ash/components/settings:test_support",
+    "//chromeos/ash/services/network_config:in_process_instance",
+    "//chromeos/services/network_config/public/cpp:test_support",
     "//components/account_id",
     "//components/policy/proto",
     "//components/prefs",
diff --git a/chrome/browser/ash/test/glic_user_session_test_helper.cc b/chrome/browser/ash/test/glic_user_session_test_helper.cc
index e865d000..14977755 100644
--- a/chrome/browser/ash/test/glic_user_session_test_helper.cc
+++ b/chrome/browser/ash/test/glic_user_session_test_helper.cc
@@ -9,9 +9,13 @@
 
 #include "base/check.h"
 #include "chrome/browser/ash/login/users/scoped_account_id_annotator.h"
+#include "chrome/browser/ash/test/glic_user_session_test_helper.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
+#include "chromeos/ash/components/network/network_handler_test_helper.h"
+#include "chromeos/ash/services/network_config/in_process_instance.h"
+#include "chromeos/services/network_config/public/cpp/fake_cros_network_config.h"
 #include "components/account_id/account_id.h"
 #include "components/session_manager/core/fake_session_manager_delegate.h"
 #include "components/session_manager/core/session_manager.h"
@@ -41,6 +45,13 @@
       << "PreProfileSetUp must be called before profile is created";
   profile_manager_observation_.Observe(profile_manager);
 
+  network_handler_test_helper_ =
+      std::make_unique<ash::NetworkHandlerTestHelper>();
+  fake_cros_network_config_ =
+      std::make_unique<chromeos::network_config::FakeCrosNetworkConfig>();
+  ash::network_config::OverrideInProcessInstanceForTesting(
+      fake_cros_network_config_.get());
+
   session_manager_ = std::make_unique<session_manager::SessionManager>(
       std::make_unique<session_manager::FakeSessionManagerDelegate>());
 
@@ -77,6 +88,10 @@
 
   session_manager_.reset();
   user_manager_.Reset();
+
+  ash::network_config::OverrideInProcessInstanceForTesting(nullptr);
+  fake_cros_network_config_.reset();
+  network_handler_test_helper_.reset();
 }
 
 void GlicUserSessionTestHelper::OnProfileManagerDestroying() {
diff --git a/chrome/browser/ash/test/glic_user_session_test_helper.h b/chrome/browser/ash/test/glic_user_session_test_helper.h
index 460585a5..6c2bae9 100644
--- a/chrome/browser/ash/test/glic_user_session_test_helper.h
+++ b/chrome/browser/ash/test/glic_user_session_test_helper.h
@@ -14,12 +14,17 @@
 class ProfileManager;
 class ProfileManagerObserver;
 
+namespace chromeos::network_config {
+class FakeCrosNetworkConfig;
+}  // namespace chromeos::network_config
+
 namespace session_manager {
 class SessionManager;
 }  // namespace session_manager
 
 namespace ash {
 
+class NetworkHandlerTestHelper;
 class ScopedAccountIdAnnotator;
 
 // Sets up user session for GLIC.
@@ -54,6 +59,10 @@
 
   std::unique_ptr<ScopedAccountIdAnnotator> scoped_account_id_annotator_;
   bool need_post_profile_teardown_ = false;
+
+  std::unique_ptr<ash::NetworkHandlerTestHelper> network_handler_test_helper_;
+  std::unique_ptr<chromeos::network_config::FakeCrosNetworkConfig>
+      fake_cros_network_config_;
 };
 
 }  // namespace ash
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarCoordinator.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarCoordinator.java
index 5f7e007e..b921835f 100644
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarCoordinator.java
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarCoordinator.java
@@ -80,6 +80,8 @@
     private final ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
     private final SimpleRecyclerViewAdapter mItemsAdapter;
     private final BookmarkBarItemsLayoutManager mBookmarkBarItemsLayoutManager;
+    private final BookmarkButtonItemAnimator mItemAnimator;
+    private final RecyclerView mItemsContainer;
     private final BookmarkBarMediator mMediator;
     private final BookmarkBar mView;
     private final FrameLayout mContentContainer;
@@ -204,20 +206,21 @@
                 BookmarkBarUtils.ViewType.ITEM,
                 this::inflateBookmarkBarButton,
                 BookmarkBarButtonViewBinder::bind);
-        final RecyclerView itemsContainer =
-                mViewResourceFrameLayout.findViewById(R.id.bookmark_bar_items_container);
-        itemsContainer.setAdapter(mItemsAdapter);
+        mItemsContainer = mViewResourceFrameLayout.findViewById(R.id.bookmark_bar_items_container);
+        mItemsContainer.setAdapter(mItemsAdapter);
         mBookmarkBarItemsLayoutManager = new BookmarkBarItemsLayoutManager(activity);
         mBookmarkBarItemsLayoutManager.setItemWidthConstraints(
                 activity.getResources().getDimensionPixelSize(R.dimen.bookmark_bar_item_min_width),
                 activity.getResources().getDimensionPixelSize(R.dimen.bookmark_bar_item_max_width));
-        itemsContainer.setLayoutManager(mBookmarkBarItemsLayoutManager);
+        mItemsContainer.setLayoutManager(mBookmarkBarItemsLayoutManager);
 
         // NOTE: Scrolling isn't supported and items rarely change so item view caching is disabled.
-        itemsContainer.getRecycledViewPool().setMaxRecycledViews(BookmarkBarUtils.ViewType.ITEM, 0);
-        itemsContainer.setItemViewCacheSize(0);
-        itemsContainer.setItemAnimator(
-                new BookmarkButtonItemAnimator(this::handleBookmarkBarChange));
+        mItemAnimator = new BookmarkButtonItemAnimator(this::handleBookmarkBarChange);
+        mItemsContainer
+                .getRecycledViewPool()
+                .setMaxRecycledViews(BookmarkBarUtils.ViewType.ITEM, 0);
+        mItemsContainer.setItemViewCacheSize(0);
+        mItemsContainer.setItemAnimator(mItemAnimator);
 
         Supplier<Pair<Integer, Integer>> controlsHeightSupplier =
                 () ->
@@ -240,7 +243,7 @@
                         currentTab,
                         bookmarkOpener,
                         bookmarkManagerOpenerSupplier,
-                        itemsContainer,
+                        mItemsContainer,
                         mView);
         PropertyModelChangeProcessor.create(model, mView, BookmarkBarViewBinder::bind);
 
@@ -294,6 +297,14 @@
 
     /** Destroys the bookmark bar coordinator. */
     public void destroy() {
+        // Stop all pending animations and remove animator to stop any running async update calls.
+        if (mItemsContainer != null) {
+            mItemsContainer.setItemAnimator(null);
+            if (mItemAnimator != null) {
+                mItemAnimator.destroy();
+            }
+        }
+
         mTopControlsStacker.removeControl(this);
         mItemsAdapter.destroy();
         mMediator.destroy();
@@ -703,6 +714,7 @@
      */
     private static class BookmarkButtonItemAnimator extends DefaultItemAnimator {
         private final Runnable mPostAnimationRunnable;
+        private boolean mIsDestroyed;
 
         BookmarkButtonItemAnimator(Runnable mPostAnimationRunnable) {
             this.mPostAnimationRunnable = mPostAnimationRunnable;
@@ -711,7 +723,14 @@
         @Override
         public void onAnimationFinished(@NonNull RecyclerView.ViewHolder viewHolder) {
             super.onAnimationFinished(viewHolder);
-            mPostAnimationRunnable.run();
+            if (!mIsDestroyed) {
+                mPostAnimationRunnable.run();
+            }
+        }
+
+        public void destroy() {
+            super.endAnimations();
+            mIsDestroyed = true;
         }
     }
 
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarSceneLayer.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarSceneLayer.java
index cce6840..9bbc4ee 100644
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarSceneLayer.java
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarSceneLayer.java
@@ -63,6 +63,9 @@
 
     /** Push all information about the scene layer / snapshot to native-side code at once. */
     public void updateProperties(PropertyModel model) {
+        if (mNativePtr == 0) return;
+        if (!mIsVisible) return;
+
         BookmarkBarSceneLayerJni.get()
                 .updateBookmarkBarLayer(
                         mNativePtr,
@@ -98,6 +101,7 @@
 
     @Override
     public void setContentTree(SceneLayer contentTree) {
+        if (mNativePtr == 0) return;
         BookmarkBarSceneLayerJni.get().setContentTree(mNativePtr, contentTree);
     }
 
diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/TopControlLayer.java b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/TopControlLayer.java
index a08e207..41def0f 100644
--- a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/TopControlLayer.java
+++ b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/TopControlLayer.java
@@ -128,4 +128,16 @@
      *     or hidden. This is used when layer is showing / hiding, so it can change its visibility.
      */
     default void onBrowserControlsOffsetUpdate(int layerYOffset, boolean reachRestingPosition) {}
+
+    /**
+     * Interface method to dispatch a signal that the browser controls is expecting an animated
+     * height update. This is dispatched in the same loop when a layer has updated their height, and
+     * before any {@link #onBrowserControlsOffsetUpdate} is called.
+     *
+     * <p>This is useful for an layer that need to respond to animation e.g. by removing the offset
+     * tags from the scene layer.
+     *
+     * @param latestYOffset The last Y offset of the current layer known by the stacker.
+     */
+    default void prepForHeightAdjustmentAnimation(int latestYOffset) {}
 }
diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/TopControlsStacker.java b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/TopControlsStacker.java
index cf6a046..7974fc44c 100644
--- a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/TopControlsStacker.java
+++ b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/TopControlsStacker.java
@@ -134,6 +134,7 @@
     private int mMinHeight;
     private @Nullable BrowserControlsOffsetTagsInfo mTopControlsOffsetTagInfo;
     private boolean mIsMinHeightShrinking;
+    private boolean mHasAnimationLayer;
 
     /**
      * Constructs the top controls stacker, which is used to calculate heights and offsets for any
@@ -220,6 +221,7 @@
 
         recalculateHeights();
         recalculateLayerRestingOffsets();
+        prepForAnimation(animate);
         updateTopControlsHeight(animate);
 
         // When reposition happening when browser controls is overriding offsets, we need to
@@ -288,6 +290,34 @@
         mMinHeight = minHeight;
     }
 
+    private void prepForAnimation(boolean requireAnimation) {
+        // We are iterating through the current list to ensure |mHasAnimationLayer| stays consistent
+        // with the layer's visibility. This is needed even when animation is not required.
+        mHasAnimationLayer = false;
+        for (@TopControlType int type : STACK_ORDER) {
+            TopControlLayer layer = mControls.get(type);
+            if (layer == null) continue;
+            mHasAnimationLayer =
+                    layer.getTopControlVisibility() != TopControlVisibility.VISIBLE
+                            && layer.getTopControlVisibility() != TopControlVisibility.HIDDEN;
+            if (mHasAnimationLayer) {
+                break;
+            }
+        }
+
+        // If an animation exists, notify all the layers. This gives then a chance to remove
+        // their offset tags and their latest yOffset before an animation starts.
+        if (mHasAnimationLayer && requireAnimation) {
+            for (@TopControlType int type : STACK_ORDER) {
+                TopControlLayer layer = mControls.get(type);
+                if (layer == null) continue;
+
+                int currentYOffset = mLayerYOffset.get(type, -layer.getTopControlHeight());
+                layer.prepForHeightAdjustmentAnimation(currentYOffset);
+            }
+        }
+    }
+
     // Calculate the layer's resting offsets assuming the top control is fully shown.
     private void recalculateLayerRestingOffsets() {
         int cumulativeHeight = 0;
@@ -311,7 +341,8 @@
     }
 
     // Core logic to dispatch offset to top control layers. This handles offsets either during user
-    // scrolling, or a browser or render driven animation is ran.
+    // scrolling, or a browser or render driven animation is ran. This method depends on the
+    // |mHasAnimationLayer|, which is populated in prepForAnimation.
     private void repositionLayers(
             int initialTopOffset,
             int initialTopControlsMinHeightOffset,
@@ -332,18 +363,7 @@
         // 0. Checking if we are in animation. Render driven offsets currently cannot handle layer
         // animation at different speed. So if any of the layer attempts to animation, we'll have to
         // let browser take over the offset calculation.
-        boolean hasAnimatingLayer = false;
-        for (@TopControlType int type : STACK_ORDER) {
-            TopControlLayer layer = mControls.get(type);
-            if (layer == null) continue;
-            hasAnimatingLayer =
-                    layer.getTopControlVisibility() != TopControlVisibility.VISIBLE
-                            && layer.getTopControlVisibility() != TopControlVisibility.HIDDEN;
-            if (hasAnimatingLayer) {
-                break;
-            }
-        }
-        offsetsAppliedByBrowser |= hasAnimatingLayer;
+        offsetsAppliedByBrowser |= mHasAnimationLayer;
 
         // 1. Calculate the offset based on the current layer position. In this step, the controls
         // are classified into scrollable and non-scrollable layer, and all the layers are display
@@ -362,7 +382,7 @@
         // updates to layer that's changed from visible -> hidden.
 
         // The algorithm adjust layer's offset based on whether the control is showing or hiding.
-        if (hasAnimatingLayer && initialTopOffset != 0) {
+        if (mHasAnimationLayer && initialTopOffset != 0) {
             // When animated size change, the browser controls will try to ensure it snaps to its
             // resting position. We'll use the minHeight as comparison first, then compare the
             // topOffset.
diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/TopControlsStackerUnitTest.java b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/TopControlsStackerUnitTest.java
index 9405eae..44f04c50 100644
--- a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/TopControlsStackerUnitTest.java
+++ b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/TopControlsStackerUnitTest.java
@@ -57,6 +57,7 @@
         private @TopControlVisibility int mVisibility;
         private @Nullable BrowserControlsOffsetTagsInfo mOffsetTagsInfo;
         private int mLatestYOffset = OFFSET_NOT_OBSERVED;
+        private int mPrepForAnimationYOffset = OFFSET_NOT_OBSERVED;
         private boolean mAtRestingPosition;
 
         TestLayer(
@@ -110,6 +111,11 @@
             mAtRestingPosition = reachRestingPosition;
         }
 
+        @Override
+        public void prepForHeightAdjustmentAnimation(int latestYOffset) {
+            mPrepForAnimationYOffset = latestYOffset;
+        }
+
         // Assert methods
 
         void assertHasOffsetTags(@Nullable BrowserControlsOffsetTagsInfo offsetTagsInfo) {
@@ -137,6 +143,13 @@
             return this;
         }
 
+        void assertPrepForAnimation(int expectedYOffset) {
+            assertEquals(
+                    mName + " should have prepForHeightAdjustmentAnimation called.",
+                    expectedYOffset,
+                    mPrepForAnimationYOffset);
+        }
+
         // Factory methods
 
         static TestLayer statusIndicatorLayer() {
@@ -838,6 +851,78 @@
     }
 
     @Test
+    public void testPrepForAnimation() {
+        doReturn(false).when(mBrowserControlsSizer).offsetOverridden();
+
+        var simulator = new TestBrowserControlsOffsetHelper();
+        TestLayer tabStrip = TestLayer.tabStripLayer();
+        TestLayer toolbar = TestLayer.toolbarLayer();
+
+        mTopControlsStacker.addControl(tabStrip);
+        mTopControlsStacker.addControl(toolbar);
+        mTopControlsStacker.requestLayerUpdate(false);
+        simulator.commitCurrentOffset();
+
+        // Animate hiding tab strip. The call to prepForAnimation should happen here.
+        tabStrip.mVisibility = TopControlVisibility.HIDING_TOP_ANCHOR;
+        mTopControlsStacker.requestLayerUpdate(true);
+
+        // Both layers should have their prepForHeightAdjustmentAnimation called.
+        tabStrip.assertPrepForAnimation(0);
+        toolbar.assertPrepForAnimation(50);
+    }
+
+    @Test
+    public void testPrepForAnimation_ControlsScrolledOff() {
+        doReturn(false).when(mBrowserControlsSizer).offsetOverridden();
+
+        var simulator = new TestBrowserControlsOffsetHelper();
+        TestLayer tabStrip = TestLayer.tabStripLayer();
+        TestLayer toolbar = TestLayer.toolbarLayer();
+
+        mTopControlsStacker.addControl(tabStrip);
+        mTopControlsStacker.addControl(toolbar);
+        mTopControlsStacker.requestLayerUpdate(false);
+        simulator.commitCurrentOffset();
+
+        // Scroll when toolbar and tab strip all hidden.
+        simulator.scrollBy(-150);
+
+        tabStrip.assertOffset(-50).assertAtResting(true);
+        toolbar.assertOffset(-100).assertAtResting(true);
+
+        // Animate hiding tab strip. The call to prepForAnimation should happen here.
+        tabStrip.mVisibility = TopControlVisibility.HIDING_TOP_ANCHOR;
+        mTopControlsStacker.requestLayerUpdate(true);
+
+        // Both layers should have their prepForHeightAdjustmentAnimation called.
+        tabStrip.assertPrepForAnimation(-50);
+        toolbar.assertPrepForAnimation(-100);
+    }
+
+    @Test
+    public void testPrepForAnimation_NoAnimation() {
+        doReturn(false).when(mBrowserControlsSizer).offsetOverridden();
+
+        var simulator = new TestBrowserControlsOffsetHelper();
+        TestLayer tabStrip = TestLayer.tabStripLayer();
+        TestLayer toolbar = TestLayer.toolbarLayer();
+
+        mTopControlsStacker.addControl(tabStrip);
+        mTopControlsStacker.addControl(toolbar);
+        mTopControlsStacker.requestLayerUpdate(false);
+        simulator.commitCurrentOffset();
+
+        // Animate hiding tab strip. The call to prepForAnimation should not happen here.
+        tabStrip.mVisibility = TopControlVisibility.HIDING_TOP_ANCHOR;
+        mTopControlsStacker.requestLayerUpdate(false);
+
+        // Both layers should not have their prepForHeightAdjustmentAnimation called.
+        tabStrip.assertPrepForAnimation(OFFSET_NOT_OBSERVED);
+        toolbar.assertPrepForAnimation(OFFSET_NOT_OBSERVED);
+    }
+
+    @Test
     public void repositionLayer_Animate_ShowingTopAnchor() {
         // For this test case, we'll assume the render can respond to the animation.
         doReturn(false).when(mBrowserControlsSizer).offsetOverridden();
diff --git a/chrome/browser/chromeos/extensions/info_private/info_private_apitest.cc b/chrome/browser/chromeos/extensions/info_private/info_private_apitest.cc
index 2a302ca..164faf5 100644
--- a/chrome/browser/chromeos/extensions/info_private/info_private_apitest.cc
+++ b/chrome/browser/chromeos/extensions/info_private/info_private_apitest.cc
@@ -200,8 +200,6 @@
       << message_;
 }
 
-// TODO(crbug.com/40564126): Excluded from Mash because pointer events
-// aren't seen.
 IN_PROC_BROWSER_TEST_F(ChromeOSInfoPrivateTest, StylusSeen) {
   ui::DeviceDataManagerTestApi test_api;
   ui::TouchscreenDevice touchscreen(1,
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager.h b/chrome/browser/chromeos/policy/dlp/dlp_content_manager.h
index d9c9461..9259ed0 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_content_manager.h
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager.h
@@ -442,7 +442,6 @@
 
   // Keeps track of the contents for which the user allowed the action after
   // being shown a warning for each type of restriction.
-  // TODO(crbug.com/1264803): Change to DlpConfidentialContentsCache
   DlpConfidentialContentsCache user_allowed_contents_cache_;
 
   // List of the currently running screen shares.
diff --git a/chrome/browser/component_updater/pki_metadata_component_installer.cc b/chrome/browser/component_updater/pki_metadata_component_installer.cc
index 81cc9e8..5c68841 100644
--- a/chrome/browser/component_updater/pki_metadata_component_installer.cc
+++ b/chrome/browser/component_updater/pki_metadata_component_installer.cc
@@ -33,6 +33,8 @@
 #include "base/time/time.h"
 #include "base/values.h"
 #include "chrome/browser/browser_features.h"
+#include "chrome/browser/component_updater/pki_metadata_component_installer_policy.h"
+#include "chrome/browser/component_updater/pki_metadata_fastpush_component_installer_policy.h"
 #include "chrome/browser/net/key_pinning.pb.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "content/public/browser/network_service_instance.h"
@@ -88,20 +90,6 @@
 // result in sending useless data for TAIs that don't work anymore.
 constexpr base::TimeDelta kMaxMtcMetadataAge = base::Days(7);
 
-// The SHA256 of the SubjectPublicKeyInfo used to sign the extension.
-// The extension id is: efniojlnjndmcbiieegkicadnoecjjef
-const uint8_t kPKIMetadataPublicKeySHA256[32] = {
-    0x45, 0xd8, 0xe9, 0xbd, 0x9d, 0x3c, 0x21, 0x88, 0x44, 0x6a, 0x82,
-    0x03, 0xde, 0x42, 0x99, 0x45, 0x66, 0x25, 0xfe, 0xb3, 0xd1, 0xf8,
-    0x11, 0x65, 0xb4, 0x6f, 0xd3, 0x1b, 0x21, 0x89, 0xbe, 0x9c};
-
-// The SHA256 of the SubjectPublicKeyInfo used to sign the fastpush extension.
-// The extension id is: oaanfgijljhkdknnacjidbpmmgnghhjj
-constexpr uint8_t kPKIMetadataFastpushPublicKeySHA256[32] = {
-    0xe0, 0x0d, 0x56, 0x89, 0xb9, 0x7a, 0x3a, 0xdd, 0x02, 0x98, 0x31,
-    0xfc, 0xc6, 0xd6, 0x77, 0x99, 0x71, 0x4d, 0xf9, 0xc7, 0x7e, 0xa2,
-    0x29, 0xcd, 0x41, 0x2b, 0x51, 0xee, 0x7d, 0xe8, 0x12, 0x5c};
-
 const base::FilePath::CharType kCTConfigProtoFileName[] =
     FILE_PATH_LITERAL("ct_config.pb");
 
@@ -207,9 +195,6 @@
 
 namespace component_updater {
 
-// ---------------------------------------------------------------------------
-// PKIMetadataComponentInstallerService:
-
 // static
 PKIMetadataComponentInstallerService*
 PKIMetadataComponentInstallerService::GetInstance() {
@@ -779,86 +764,20 @@
   }
 }
 
-// ---------------------------------------------------------------------------
-// PKIMetadataComponentInstallerPolicy:
-
-PKIMetadataComponentInstallerPolicy::PKIMetadataComponentInstallerPolicy() =
-    default;
-
-PKIMetadataComponentInstallerPolicy::~PKIMetadataComponentInstallerPolicy() =
-    default;
-
 // static
 std::vector<std::vector<uint8_t>>
-PKIMetadataComponentInstallerPolicy::BytesArrayFromProtoBytesForTesting(
+PKIMetadataComponentInstallerService::BytesArrayFromProtoBytesForTesting(
     const google::protobuf::RepeatedPtrField<std::string>& proto_bytes) {
   return BytesArrayFromProtoBytes(proto_bytes);
 }
 
 // static
-std::vector<net::SHA256HashValue> PKIMetadataComponentInstallerPolicy::
+std::vector<net::SHA256HashValue> PKIMetadataComponentInstallerService::
     SHA256HashValueArrayFromProtoBytesForTesting(
         const google::protobuf::RepeatedPtrField<std::string>& proto_bytes) {
   return SHA256HashValueArrayFromProtoBytes(proto_bytes);
 }
 
-bool PKIMetadataComponentInstallerPolicy::
-    SupportsGroupPolicyEnabledComponentUpdates() const {
-  return true;
-}
-
-bool PKIMetadataComponentInstallerPolicy::RequiresNetworkEncryption() const {
-  return false;
-}
-
-update_client::CrxInstaller::Result
-PKIMetadataComponentInstallerPolicy::OnCustomInstall(
-    const base::Value::Dict& /* manifest */,
-    const base::FilePath& /* install_dir */) {
-  return update_client::CrxInstaller::Result(0);  // Nothing custom here.
-}
-
-void PKIMetadataComponentInstallerPolicy::OnCustomUninstall() {}
-
-void PKIMetadataComponentInstallerPolicy::ComponentReady(
-    const base::Version& version,
-    const base::FilePath& install_dir,
-    base::Value::Dict /* manifest */) {
-  PKIMetadataComponentInstallerService::GetInstance()->OnComponentReady(
-      install_dir);
-}
-
-// Called during startup and installation before ComponentReady().
-bool PKIMetadataComponentInstallerPolicy::VerifyInstallation(
-    const base::Value::Dict& /* manifest */,
-    const base::FilePath& install_dir) const {
-  if (!base::PathExists(install_dir)) {
-    return false;
-  }
-
-  return true;
-}
-
-base::FilePath PKIMetadataComponentInstallerPolicy::GetRelativeInstallDir()
-    const {
-  return base::FilePath(FILE_PATH_LITERAL("PKIMetadata"));
-}
-
-void PKIMetadataComponentInstallerPolicy::GetHash(
-    std::vector<uint8_t>* hash) const {
-  hash->assign(std::begin(kPKIMetadataPublicKeySHA256),
-               std::end(kPKIMetadataPublicKeySHA256));
-}
-
-std::string PKIMetadataComponentInstallerPolicy::GetName() const {
-  return "PKI Metadata";
-}
-
-update_client::InstallerAttributes
-PKIMetadataComponentInstallerPolicy::GetInstallerAttributes() const {
-  return update_client::InstallerAttributes();
-}
-
 void MaybeRegisterPKIMetadataComponent(ComponentUpdateService* cus) {
   auto installer = base::MakeRefCounted<ComponentInstaller>(
       std::make_unique<PKIMetadataComponentInstallerPolicy>());
@@ -873,72 +792,4 @@
 #endif  // BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
 }
 
-#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
-// ---------------------------------------------------------------------------
-// PKIMetadataFastpushComponentInstallerPolicy:
-
-PKIMetadataFastpushComponentInstallerPolicy::
-    PKIMetadataFastpushComponentInstallerPolicy() = default;
-
-PKIMetadataFastpushComponentInstallerPolicy::
-    ~PKIMetadataFastpushComponentInstallerPolicy() = default;
-
-bool PKIMetadataFastpushComponentInstallerPolicy::
-    SupportsGroupPolicyEnabledComponentUpdates() const {
-  return true;
-}
-
-bool PKIMetadataFastpushComponentInstallerPolicy::RequiresNetworkEncryption()
-    const {
-  return false;
-}
-
-update_client::CrxInstaller::Result
-PKIMetadataFastpushComponentInstallerPolicy::OnCustomInstall(
-    const base::Value::Dict& /* manifest */,
-    const base::FilePath& /* install_dir */) {
-  return update_client::CrxInstaller::Result(0);  // Nothing custom here.
-}
-
-void PKIMetadataFastpushComponentInstallerPolicy::OnCustomUninstall() {}
-
-void PKIMetadataFastpushComponentInstallerPolicy::ComponentReady(
-    const base::Version& version,
-    const base::FilePath& install_dir,
-    base::Value::Dict /* manifest */) {
-  PKIMetadataComponentInstallerService::GetInstance()->OnFastpushComponentReady(
-      install_dir);
-}
-
-// Called during startup and installation before ComponentReady().
-bool PKIMetadataFastpushComponentInstallerPolicy::VerifyInstallation(
-    const base::Value::Dict& /* manifest */,
-    const base::FilePath& install_dir) const {
-  if (!base::PathExists(install_dir)) {
-    return false;
-  }
-
-  return true;
-}
-
-base::FilePath
-PKIMetadataFastpushComponentInstallerPolicy::GetRelativeInstallDir() const {
-  return base::FilePath(FILE_PATH_LITERAL("PKIMetadataFastpush"));
-}
-
-void PKIMetadataFastpushComponentInstallerPolicy::GetHash(
-    std::vector<uint8_t>* hash) const {
-  *hash = base::ToVector(kPKIMetadataFastpushPublicKeySHA256);
-}
-
-std::string PKIMetadataFastpushComponentInstallerPolicy::GetName() const {
-  return "PKI Metadata Fastpush";
-}
-
-update_client::InstallerAttributes
-PKIMetadataFastpushComponentInstallerPolicy::GetInstallerAttributes() const {
-  return update_client::InstallerAttributes();
-}
-#endif  // BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
-
 }  // namespace component_updater
diff --git a/chrome/browser/component_updater/pki_metadata_component_installer.h b/chrome/browser/component_updater/pki_metadata_component_installer.h
index 8a5f213..32bd233c 100644
--- a/chrome/browser/component_updater/pki_metadata_component_installer.h
+++ b/chrome/browser/component_updater/pki_metadata_component_installer.h
@@ -13,7 +13,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/sequence_checker.h"
-#include "base/values.h"
 #include "components/component_updater/component_installer.h"
 #include "mojo/public/cpp/base/proto_wrapper.h"
 #include "net/base/hash_value.h"
@@ -44,6 +43,15 @@
   // Returns the live server instance, creating it if it does not exist.
   static PKIMetadataComponentInstallerService* GetInstance();
 
+  // Wraps BytesArrayFromProtoBytes, exposed for testing.
+  static std::vector<std::vector<uint8_t>> BytesArrayFromProtoBytesForTesting(
+      const google::protobuf::RepeatedPtrField<std::string>& proto_bytes);
+
+  // Wraps SHA256HashValueArrayFromProtoBytes, exposed for testing.
+  static std::vector<net::SHA256HashValue>
+  SHA256HashValueArrayFromProtoBytesForTesting(
+      const google::protobuf::RepeatedPtrField<std::string>& proto_bytes);
+
   PKIMetadataComponentInstallerService();
   ~PKIMetadataComponentInstallerService() = delete;
 
@@ -162,79 +170,6 @@
       this};
 };
 
-// Component installer policy for the PKIMetadata component. This component
-// includes any dynamically updateable needed for PKI policies enforcement.
-class PKIMetadataComponentInstallerPolicy : public ComponentInstallerPolicy {
- public:
-  PKIMetadataComponentInstallerPolicy();
-  PKIMetadataComponentInstallerPolicy(
-      const PKIMetadataComponentInstallerPolicy&) = delete;
-  PKIMetadataComponentInstallerPolicy operator=(
-      const PKIMetadataComponentInstallerPolicy&) = delete;
-  ~PKIMetadataComponentInstallerPolicy() override;
-
-  // Wraps BytesArrayFromProtoBytes, exposed for testing.
-  static std::vector<std::vector<uint8_t>> BytesArrayFromProtoBytesForTesting(
-      const google::protobuf::RepeatedPtrField<std::string>& proto_bytes);
-
-  // Wraps SHA256HashValueArrayFromProtoBytes, exposed for testing.
-  static std::vector<net::SHA256HashValue>
-  SHA256HashValueArrayFromProtoBytesForTesting(
-      const google::protobuf::RepeatedPtrField<std::string>& proto_bytes);
-
- private:
-  // ComponentInstallerPolicy methods:
-  bool SupportsGroupPolicyEnabledComponentUpdates() const override;
-  bool RequiresNetworkEncryption() const override;
-  update_client::CrxInstaller::Result OnCustomInstall(
-      const base::Value::Dict& manifest,
-      const base::FilePath& install_dir) override;
-  void OnCustomUninstall() override;
-  bool VerifyInstallation(const base::Value::Dict& manifest,
-                          const base::FilePath& install_dir) const override;
-  void ComponentReady(const base::Version& version,
-                      const base::FilePath& install_dir,
-                      base::Value::Dict manifest) override;
-  base::FilePath GetRelativeInstallDir() const override;
-  void GetHash(std::vector<uint8_t>* hash) const override;
-  std::string GetName() const override;
-  update_client::InstallerAttributes GetInstallerAttributes() const override;
-};
-
-#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
-// Component installer policy for the PKIMetadataFastpush component. This
-// component goes along with PKIMetadata component, but includes data that
-// needs lower update latency.
-class PKIMetadataFastpushComponentInstallerPolicy
-    : public ComponentInstallerPolicy {
- public:
-  PKIMetadataFastpushComponentInstallerPolicy();
-  PKIMetadataFastpushComponentInstallerPolicy(
-      const PKIMetadataFastpushComponentInstallerPolicy&) = delete;
-  PKIMetadataFastpushComponentInstallerPolicy operator=(
-      const PKIMetadataFastpushComponentInstallerPolicy&) = delete;
-  ~PKIMetadataFastpushComponentInstallerPolicy() override;
-
- private:
-  // ComponentInstallerPolicy methods:
-  bool SupportsGroupPolicyEnabledComponentUpdates() const override;
-  bool RequiresNetworkEncryption() const override;
-  update_client::CrxInstaller::Result OnCustomInstall(
-      const base::Value::Dict& manifest,
-      const base::FilePath& install_dir) override;
-  void OnCustomUninstall() override;
-  bool VerifyInstallation(const base::Value::Dict& manifest,
-                          const base::FilePath& install_dir) const override;
-  void ComponentReady(const base::Version& version,
-                      const base::FilePath& install_dir,
-                      base::Value::Dict manifest) override;
-  base::FilePath GetRelativeInstallDir() const override;
-  void GetHash(std::vector<uint8_t>* hash) const override;
-  std::string GetName() const override;
-  update_client::InstallerAttributes GetInstallerAttributes() const override;
-};
-#endif  // BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
-
 void MaybeRegisterPKIMetadataComponent(ComponentUpdateService* cus);
 
 }  // namespace component_updater
diff --git a/chrome/browser/component_updater/pki_metadata_component_installer_policy.cc b/chrome/browser/component_updater/pki_metadata_component_installer_policy.cc
new file mode 100644
index 0000000..55ca4d9
--- /dev/null
+++ b/chrome/browser/component_updater/pki_metadata_component_installer_policy.cc
@@ -0,0 +1,87 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/component_updater/pki_metadata_component_installer_policy.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "chrome/browser/component_updater/pki_metadata_component_installer.h"
+
+namespace {
+
+// The SHA256 of the SubjectPublicKeyInfo used to sign the extension.
+// The extension id is: efniojlnjndmcbiieegkicadnoecjjef
+const uint8_t kPKIMetadataPublicKeySHA256[32] = {
+    0x45, 0xd8, 0xe9, 0xbd, 0x9d, 0x3c, 0x21, 0x88, 0x44, 0x6a, 0x82,
+    0x03, 0xde, 0x42, 0x99, 0x45, 0x66, 0x25, 0xfe, 0xb3, 0xd1, 0xf8,
+    0x11, 0x65, 0xb4, 0x6f, 0xd3, 0x1b, 0x21, 0x89, 0xbe, 0x9c};
+
+}  // namespace
+
+namespace component_updater {
+
+PKIMetadataComponentInstallerPolicy::PKIMetadataComponentInstallerPolicy() =
+    default;
+
+PKIMetadataComponentInstallerPolicy::~PKIMetadataComponentInstallerPolicy() =
+    default;
+
+bool PKIMetadataComponentInstallerPolicy::
+    SupportsGroupPolicyEnabledComponentUpdates() const {
+  return true;
+}
+
+bool PKIMetadataComponentInstallerPolicy::RequiresNetworkEncryption() const {
+  return false;
+}
+
+update_client::CrxInstaller::Result
+PKIMetadataComponentInstallerPolicy::OnCustomInstall(
+    const base::Value::Dict& /* manifest */,
+    const base::FilePath& /* install_dir */) {
+  return update_client::CrxInstaller::Result(0);  // Nothing custom here.
+}
+
+void PKIMetadataComponentInstallerPolicy::OnCustomUninstall() {}
+
+void PKIMetadataComponentInstallerPolicy::ComponentReady(
+    const base::Version& version,
+    const base::FilePath& install_dir,
+    base::Value::Dict /* manifest */) {
+  PKIMetadataComponentInstallerService::GetInstance()->OnComponentReady(
+      install_dir);
+}
+
+// Called during startup and installation before ComponentReady().
+bool PKIMetadataComponentInstallerPolicy::VerifyInstallation(
+    const base::Value::Dict& /* manifest */,
+    const base::FilePath& install_dir) const {
+  if (!base::PathExists(install_dir)) {
+    return false;
+  }
+
+  return true;
+}
+
+base::FilePath PKIMetadataComponentInstallerPolicy::GetRelativeInstallDir()
+    const {
+  return base::FilePath(FILE_PATH_LITERAL("PKIMetadata"));
+}
+
+void PKIMetadataComponentInstallerPolicy::GetHash(
+    std::vector<uint8_t>* hash) const {
+  hash->assign(std::begin(kPKIMetadataPublicKeySHA256),
+               std::end(kPKIMetadataPublicKeySHA256));
+}
+
+std::string PKIMetadataComponentInstallerPolicy::GetName() const {
+  return "PKI Metadata";
+}
+
+update_client::InstallerAttributes
+PKIMetadataComponentInstallerPolicy::GetInstallerAttributes() const {
+  return update_client::InstallerAttributes();
+}
+
+}  // namespace component_updater
diff --git a/chrome/browser/component_updater/pki_metadata_component_installer_policy.h b/chrome/browser/component_updater/pki_metadata_component_installer_policy.h
new file mode 100644
index 0000000..7854769
--- /dev/null
+++ b/chrome/browser/component_updater/pki_metadata_component_installer_policy.h
@@ -0,0 +1,49 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_COMPONENT_UPDATER_PKI_METADATA_COMPONENT_INSTALLER_POLICY_H_
+#define CHROME_BROWSER_COMPONENT_UPDATER_PKI_METADATA_COMPONENT_INSTALLER_POLICY_H_
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "base/values.h"
+#include "components/component_updater/component_installer.h"
+
+namespace component_updater {
+
+// Component installer policy for the PKIMetadata component. This component
+// includes any dynamically updateable needed for PKI policies enforcement.
+class PKIMetadataComponentInstallerPolicy : public ComponentInstallerPolicy {
+ public:
+  PKIMetadataComponentInstallerPolicy();
+  PKIMetadataComponentInstallerPolicy(
+      const PKIMetadataComponentInstallerPolicy&) = delete;
+  PKIMetadataComponentInstallerPolicy operator=(
+      const PKIMetadataComponentInstallerPolicy&) = delete;
+  ~PKIMetadataComponentInstallerPolicy() override;
+
+ private:
+  // ComponentInstallerPolicy methods:
+  bool SupportsGroupPolicyEnabledComponentUpdates() const override;
+  bool RequiresNetworkEncryption() const override;
+  update_client::CrxInstaller::Result OnCustomInstall(
+      const base::Value::Dict& manifest,
+      const base::FilePath& install_dir) override;
+  void OnCustomUninstall() override;
+  bool VerifyInstallation(const base::Value::Dict& manifest,
+                          const base::FilePath& install_dir) const override;
+  void ComponentReady(const base::Version& version,
+                      const base::FilePath& install_dir,
+                      base::Value::Dict manifest) override;
+  base::FilePath GetRelativeInstallDir() const override;
+  void GetHash(std::vector<uint8_t>* hash) const override;
+  std::string GetName() const override;
+  update_client::InstallerAttributes GetInstallerAttributes() const override;
+};
+
+}  // namespace component_updater
+
+#endif  // CHROME_BROWSER_COMPONENT_UPDATER_PKI_METADATA_COMPONENT_INSTALLER_POLICY_H_
diff --git a/chrome/browser/component_updater/pki_metadata_component_installer_unittest.cc b/chrome/browser/component_updater/pki_metadata_component_installer_unittest.cc
index f41e3bc..6f623aa 100644
--- a/chrome/browser/component_updater/pki_metadata_component_installer_unittest.cc
+++ b/chrome/browser/component_updater/pki_metadata_component_installer_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/values.h"
 #include "base/version.h"
 #include "chrome/browser/browser_features.h"
+#include "chrome/browser/component_updater/pki_metadata_component_installer_policy.h"
 #include "chrome/browser/net/key_pinning.pb.h"
 #include "components/certificate_transparency/certificate_transparency_config.pb.h"
 #include "components/certificate_transparency/ct_known_logs.h"
@@ -250,11 +251,11 @@
   std::vector<std::string> repeated_bytes = {bytes_as_string};
 
   EXPECT_EQ(
-      PKIMetadataComponentInstallerPolicy::BytesArrayFromProtoBytesForTesting(
+      PKIMetadataComponentInstallerService::BytesArrayFromProtoBytesForTesting(
           google::protobuf::RepeatedPtrField<std::string>(
               repeated_bytes.begin(), repeated_bytes.end())),
       test_bytes);
-  EXPECT_EQ(PKIMetadataComponentInstallerPolicy::
+  EXPECT_EQ(PKIMetadataComponentInstallerService::
                 SHA256HashValueArrayFromProtoBytesForTesting(
                     google::protobuf::RepeatedPtrField<std::string>(
                         repeated_bytes.begin(), repeated_bytes.end())),
diff --git a/chrome/browser/component_updater/pki_metadata_fastpush_component_installer_policy.cc b/chrome/browser/component_updater/pki_metadata_fastpush_component_installer_policy.cc
new file mode 100644
index 0000000..22f57b7
--- /dev/null
+++ b/chrome/browser/component_updater/pki_metadata_fastpush_component_installer_policy.cc
@@ -0,0 +1,88 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/component_updater/pki_metadata_fastpush_component_installer_policy.h"
+
+#include "base/containers/to_vector.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "chrome/browser/component_updater/pki_metadata_component_installer.h"
+
+namespace {
+
+// The SHA256 of the SubjectPublicKeyInfo used to sign the fastpush extension.
+// The extension id is: oaanfgijljhkdknnacjidbpmmgnghhjj
+constexpr uint8_t kPKIMetadataFastpushPublicKeySHA256[32] = {
+    0xe0, 0x0d, 0x56, 0x89, 0xb9, 0x7a, 0x3a, 0xdd, 0x02, 0x98, 0x31,
+    0xfc, 0xc6, 0xd6, 0x77, 0x99, 0x71, 0x4d, 0xf9, 0xc7, 0x7e, 0xa2,
+    0x29, 0xcd, 0x41, 0x2b, 0x51, 0xee, 0x7d, 0xe8, 0x12, 0x5c};
+
+}  // namespace
+
+namespace component_updater {
+
+PKIMetadataFastpushComponentInstallerPolicy::
+    PKIMetadataFastpushComponentInstallerPolicy() = default;
+
+PKIMetadataFastpushComponentInstallerPolicy::
+    ~PKIMetadataFastpushComponentInstallerPolicy() = default;
+
+bool PKIMetadataFastpushComponentInstallerPolicy::
+    SupportsGroupPolicyEnabledComponentUpdates() const {
+  return true;
+}
+
+bool PKIMetadataFastpushComponentInstallerPolicy::RequiresNetworkEncryption()
+    const {
+  return false;
+}
+
+update_client::CrxInstaller::Result
+PKIMetadataFastpushComponentInstallerPolicy::OnCustomInstall(
+    const base::Value::Dict& /* manifest */,
+    const base::FilePath& /* install_dir */) {
+  return update_client::CrxInstaller::Result(0);  // Nothing custom here.
+}
+
+void PKIMetadataFastpushComponentInstallerPolicy::OnCustomUninstall() {}
+
+void PKIMetadataFastpushComponentInstallerPolicy::ComponentReady(
+    const base::Version& version,
+    const base::FilePath& install_dir,
+    base::Value::Dict /* manifest */) {
+  PKIMetadataComponentInstallerService::GetInstance()->OnFastpushComponentReady(
+      install_dir);
+}
+
+// Called during startup and installation before ComponentReady().
+bool PKIMetadataFastpushComponentInstallerPolicy::VerifyInstallation(
+    const base::Value::Dict& /* manifest */,
+    const base::FilePath& install_dir) const {
+  if (!base::PathExists(install_dir)) {
+    return false;
+  }
+
+  return true;
+}
+
+base::FilePath
+PKIMetadataFastpushComponentInstallerPolicy::GetRelativeInstallDir() const {
+  return base::FilePath(FILE_PATH_LITERAL("PKIMetadataFastpush"));
+}
+
+void PKIMetadataFastpushComponentInstallerPolicy::GetHash(
+    std::vector<uint8_t>* hash) const {
+  *hash = base::ToVector(kPKIMetadataFastpushPublicKeySHA256);
+}
+
+std::string PKIMetadataFastpushComponentInstallerPolicy::GetName() const {
+  return "PKI Metadata Fastpush";
+}
+
+update_client::InstallerAttributes
+PKIMetadataFastpushComponentInstallerPolicy::GetInstallerAttributes() const {
+  return update_client::InstallerAttributes();
+}
+
+}  // namespace component_updater
diff --git a/chrome/browser/component_updater/pki_metadata_fastpush_component_installer_policy.h b/chrome/browser/component_updater/pki_metadata_fastpush_component_installer_policy.h
new file mode 100644
index 0000000..7bebca94
--- /dev/null
+++ b/chrome/browser/component_updater/pki_metadata_fastpush_component_installer_policy.h
@@ -0,0 +1,51 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_COMPONENT_UPDATER_PKI_METADATA_FASTPUSH_COMPONENT_INSTALLER_POLICY_H_
+#define CHROME_BROWSER_COMPONENT_UPDATER_PKI_METADATA_FASTPUSH_COMPONENT_INSTALLER_POLICY_H_
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "base/values.h"
+#include "components/component_updater/component_installer.h"
+
+namespace component_updater {
+
+// Component installer policy for the PKIMetadataFastpush component. This
+// component goes along with PKIMetadata component, but includes data that
+// needs lower update latency.
+class PKIMetadataFastpushComponentInstallerPolicy
+    : public ComponentInstallerPolicy {
+ public:
+  PKIMetadataFastpushComponentInstallerPolicy();
+  PKIMetadataFastpushComponentInstallerPolicy(
+      const PKIMetadataFastpushComponentInstallerPolicy&) = delete;
+  PKIMetadataFastpushComponentInstallerPolicy operator=(
+      const PKIMetadataFastpushComponentInstallerPolicy&) = delete;
+  ~PKIMetadataFastpushComponentInstallerPolicy() override;
+
+ private:
+  // ComponentInstallerPolicy methods:
+  bool SupportsGroupPolicyEnabledComponentUpdates() const override;
+  bool RequiresNetworkEncryption() const override;
+  update_client::CrxInstaller::Result OnCustomInstall(
+      const base::Value::Dict& manifest,
+      const base::FilePath& install_dir) override;
+  void OnCustomUninstall() override;
+  bool VerifyInstallation(const base::Value::Dict& manifest,
+                          const base::FilePath& install_dir) const override;
+  void ComponentReady(const base::Version& version,
+                      const base::FilePath& install_dir,
+                      base::Value::Dict manifest) override;
+  base::FilePath GetRelativeInstallDir() const override;
+  void GetHash(std::vector<uint8_t>* hash) const override;
+  std::string GetName() const override;
+  update_client::InstallerAttributes GetInstallerAttributes() const override;
+};
+
+}  // namespace component_updater
+
+#endif  // CHROME_BROWSER_COMPONENT_UPDATER_PKI_METADATA_FASTPUSH_COMPONENT_INSTALLER_POLICY_H_
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui.cc b/chrome/browser/contextual_tasks/contextual_tasks_ui.cc
index a272b53..66e4df4 100644
--- a/chrome/browser/contextual_tasks/contextual_tasks_ui.cc
+++ b/chrome/browser/contextual_tasks/contextual_tasks_ui.cc
@@ -95,11 +95,18 @@
   };
   source->AddLocalizedStrings(kLocalizedStrings);
 
-  // Support no file types.
-  source->AddString("composeboxImageFileTypes", "");
-  source->AddString("composeboxAttachmentFileTypes", "");
-  source->AddInteger("composeboxFileMaxSize", 0);
-  source->AddInteger("composeboxFileMaxCount", 0);
+  source->AddString(
+      "composeboxImageFileTypes",
+      contextual_tasks::kContextualTasksNextboxImageFileTypes.Get());
+  source->AddString(
+      "composeboxAttachmentFileTypes",
+      contextual_tasks::kContextualTasksNextboxAttachmentFileTypes.Get());
+  source->AddInteger(
+      "composeboxFileMaxSize",
+      contextual_tasks::kContextualTasksNextboxMaxFileSize.Get());
+  source->AddInteger(
+      "composeboxFileMaxCount",
+      contextual_tasks::kContextualTasksNextboxMaxFileCount.Get());
   source->AddBoolean("composeboxNoFlickerSuggestionsFix", false);
   // Enable typed suggest.
   source->AddBoolean("composeboxShowTypedSuggest", false);
@@ -109,7 +116,7 @@
   // Disable image context suggestions.
   source->AddBoolean("composeboxShowImageSuggest", false);
   // Disable context menu and related features.
-  source->AddBoolean("composeboxShowContextMenu", false);
+    source->AddBoolean("composeboxShowContextMenu", contextual_tasks::GetIsContextualTasksNextboxContextMenuEnabled());
   source->AddBoolean("composeboxShowContextMenuDescription", true);
   // Send event when escape is pressed.
   source->AddBoolean("composeboxCloseByEscape", true);
diff --git a/chrome/browser/device_reauth/win/authenticator_win.cc b/chrome/browser/device_reauth/win/authenticator_win.cc
index a3c68b8..b7c489ea 100644
--- a/chrome/browser/device_reauth/win/authenticator_win.cc
+++ b/chrome/browser/device_reauth/win/authenticator_win.cc
@@ -238,7 +238,6 @@
   AuthenticateWithLegacyApi(message, std::move(callback));
 }
 
-// TODO(b/349728186): Cleanup after Win11 solution is launched.
 void PerformWindowsHelloAuthenticationAsync(
     base::OnceCallback<void(bool)> callback,
     const std::u16string& message) {
@@ -346,24 +345,6 @@
   }
 }
 
-void PerformWin11Authentication(
-    const std::u16string& message,
-    base::OnceCallback<void(bool)> result_callback) {
-  PerformInteropWindowsHelloAuthenticationAsync(std::move(result_callback),
-                                                message);
-}
-
-void PerformWin10Authentication(
-    const std::u16string& message,
-    base::OnceCallback<void(bool)> result_callback) {
-  // Posting authentication using the new API on a background thread causes
-  // Windows Hello dialog not to attach to Chrome's UI and instead it is
-  // visible behind it. Running it on the default thread isn't that bad
-  // because the thread itself is not blocked and there are operations
-  // happening while the win hello dialog is visible.
-  PerformWindowsHelloAuthenticationAsync(std::move(result_callback), message);
-}
-
 }  // namespace
 
 AuthenticatorWin::AuthenticatorWin() = default;
@@ -375,19 +356,25 @@
     base::OnceCallback<void(bool)> result_callback) {
   RecordAuthenticationState(AuthenticationStateWin::kStarted);
 
-  // TODO(b/349728186): Cleanup after Win11 solution is launched.
   if (base::win::GetVersion() >= base::win::Version::WIN11) {
-    PerformWin11Authentication(
-        message, std::move(result_callback)
-                     .Then(base::BindOnce(RecordAuthenticationState,
-                                          AuthenticationStateWin::kFinished)));
+    PerformInteropWindowsHelloAuthenticationAsync(
+        std::move(result_callback)
+            .Then(base::BindOnce(RecordAuthenticationState,
+                                 AuthenticationStateWin::kFinished)),
+        message);
     return;
   }
 
-  PerformWin10Authentication(
-      message, std::move(result_callback)
-                   .Then(base::BindOnce(RecordAuthenticationState,
-                                        AuthenticationStateWin::kFinished)));
+  // Posting authentication using the new API on a background thread causes
+  // Windows Hello dialog not to attach to Chrome's UI and instead it is
+  // visible behind it. Running it on the default thread isn't that bad
+  // because the thread itself is not blocked and there are operations
+  // happening while the win hello dialog is visible.
+  PerformWindowsHelloAuthenticationAsync(
+      std::move(result_callback)
+          .Then(base::BindOnce(RecordAuthenticationState,
+                               AuthenticationStateWin::kFinished)),
+      message);
 }
 
 void AuthenticatorWin::CheckIfBiometricsAvailable(
diff --git a/chrome/browser/educational_tip/BUILD.gn b/chrome/browser/educational_tip/BUILD.gn
index b8d1bc67..809f015 100644
--- a/chrome/browser/educational_tip/BUILD.gn
+++ b/chrome/browser/educational_tip/BUILD.gn
@@ -23,6 +23,7 @@
     "java/src/org/chromium/chrome/browser/educational_tip/cards/QuickDeletePromoCoordinator.java",
     "java/src/org/chromium/chrome/browser/educational_tip/cards/TabGroupPromoCoordinator.java",
     "java/src/org/chromium/chrome/browser/educational_tip/cards/TabGroupSyncPromoCoordinator.java",
+    "java/src/org/chromium/chrome/browser/educational_tip/cards/TipsNotificationsPromoCoordinator.java",
   ]
   deps = [
     ":java_resources",
@@ -69,6 +70,7 @@
     "java/res/drawable/quick_delete_promo_logo.xml",
     "java/res/drawable/tab_group_promo_logo.xml",
     "java/res/drawable/tab_group_sync_promo_logo.xml",
+    "java/res/drawable/tips_notifications_promo_logo.xml",
     "java/res/layout/educational_tip_default_browser_bottom_sheet.xml",
     "java/res/layout/educational_tip_module_layout.xml",
     "java/res/values-night/colors.xml",
diff --git a/chrome/browser/educational_tip/java/res/drawable/tips_notifications_promo_logo.xml b/chrome/browser/educational_tip/java/res/drawable/tips_notifications_promo_logo.xml
new file mode 100644
index 0000000..6248a323
--- /dev/null
+++ b/chrome/browser/educational_tip/java/res/drawable/tips_notifications_promo_logo.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2025 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="62dp"
+    android:height="62dp"
+    android:viewportWidth="62"
+    android:viewportHeight="62">
+  <path
+      android:pathData="M8.552,0L53.448,0A8.552,8.552 0,0 1,62 8.552L62,53.448A8.552,8.552 0,0 1,53.448 62L8.552,62A8.552,8.552 0,0 1,0 53.448L0,8.552A8.552,8.552 0,0 1,8.552 0z"
+      android:fillColor="@color/educational_tip_card_logo_color_10"/>
+  <path
+      android:pathData="M24.704,4.425C25.016,4.166 25.173,4.037 25.316,3.928C28.678,1.357 33.322,1.357 36.685,3.928C36.827,4.037 36.984,4.166 37.296,4.425C37.436,4.54 37.506,4.598 37.575,4.653C39.157,5.912 41.097,6.625 43.111,6.689C43.198,6.691 43.289,6.692 43.469,6.695C43.873,6.7 44.075,6.702 44.254,6.711C48.465,6.926 52.022,9.942 52.963,14.095C53.003,14.271 53.04,14.472 53.115,14.873C53.149,15.052 53.166,15.141 53.184,15.228C53.595,17.221 54.627,19.027 56.129,20.383C56.195,20.442 56.263,20.502 56.4,20.621C56.706,20.887 56.859,21.02 56.99,21.143C60.08,24.042 60.886,28.663 58.965,32.455C58.883,32.616 58.784,32.794 58.586,33.15C58.498,33.309 58.454,33.389 58.413,33.467C57.46,35.26 57.102,37.314 57.39,39.329C57.402,39.417 57.417,39.507 57.446,39.686C57.511,40.089 57.544,40.291 57.566,40.47C58.088,44.698 55.766,48.761 51.881,50.418C51.716,50.488 51.527,50.56 51.15,50.705C50.981,50.769 50.896,50.801 50.815,50.834C48.944,51.59 47.363,52.93 46.302,54.66C46.256,54.736 46.21,54.814 46.118,54.971C45.911,55.322 45.808,55.497 45.711,55.649C43.421,59.227 39.057,60.831 35.027,59.578C34.856,59.525 34.666,59.457 34.284,59.322C34.114,59.262 34.029,59.232 33.946,59.204C32.032,58.568 29.968,58.568 28.054,59.204C27.971,59.232 27.886,59.262 27.716,59.322C27.334,59.457 27.144,59.525 26.973,59.578C22.942,60.831 18.579,59.227 16.289,55.649C16.192,55.497 16.089,55.322 15.882,54.971C15.79,54.814 15.744,54.736 15.698,54.66C14.637,52.93 13.056,51.59 11.185,50.834C11.104,50.801 11.019,50.769 10.851,50.705C10.473,50.56 10.284,50.488 10.119,50.418C6.234,48.761 3.912,44.698 4.434,40.47C4.456,40.291 4.489,40.089 4.554,39.686C4.583,39.507 4.598,39.417 4.61,39.329C4.898,37.314 4.54,35.26 3.587,33.467C3.546,33.389 3.502,33.309 3.414,33.15C3.216,32.794 3.117,32.616 3.036,32.455C1.114,28.663 1.92,24.042 5.01,21.143C5.141,21.02 5.294,20.887 5.6,20.621C5.737,20.502 5.805,20.442 5.871,20.383C7.373,19.027 8.405,17.221 8.816,15.228C8.834,15.141 8.851,15.052 8.885,14.873C8.96,14.472 8.997,14.271 9.037,14.095C9.978,9.942 13.535,6.926 17.746,6.711C17.925,6.702 18.127,6.7 18.531,6.695C18.711,6.692 18.802,6.691 18.889,6.689C20.903,6.625 22.843,5.912 24.425,4.653C24.494,4.598 24.564,4.54 24.704,4.425Z"
+      android:fillColor="@color/educational_tip_card_logo_color_3"/>
+  <group>
+    <clip-path
+        android:pathData="M14,14h34v34h-34z"/>
+    <path
+        android:pathData="M25.333,31.106C25.333,32.665 25.888,33.999 26.998,35.108C28.108,36.218 29.442,36.773 31,36.773C32.558,36.773 33.892,36.218 35.002,35.108C36.112,33.999 36.667,32.665 36.667,31.106C36.667,29.548 36.112,28.214 35.002,27.104C33.892,25.994 32.558,25.44 31,25.44C29.442,25.44 28.108,25.994 26.998,27.104C25.888,28.214 25.333,29.548 25.333,31.106ZM31,39.606C31.307,39.606 31.602,39.594 31.885,39.571C32.169,39.547 32.452,39.5 32.735,39.429L29.406,45.167C25.841,44.765 22.854,43.242 20.446,40.598C18.038,37.93 16.833,34.766 16.833,31.106C16.833,30.115 16.928,29.158 17.117,28.237C17.306,27.293 17.589,26.396 17.967,25.546L23.633,35.356C24.365,36.631 25.381,37.658 26.679,38.438C27.978,39.217 29.418,39.606 31,39.606ZM31,22.606C29.111,22.606 27.435,23.161 25.971,24.271C24.507,25.357 23.492,26.75 22.925,28.45L19.596,22.712C20.894,20.965 22.524,19.572 24.483,18.533C26.467,17.471 28.639,16.94 31,16.94C33.338,16.94 35.486,17.459 37.446,18.498C39.406,19.513 41.035,20.883 42.333,22.606H31ZM43.963,25.44C44.364,26.313 44.659,27.222 44.848,28.167C45.06,29.111 45.167,30.091 45.167,31.106C45.167,34.766 43.963,37.918 41.554,40.563C39.169,43.207 36.218,44.742 32.7,45.167L38.367,35.356C38.721,34.742 38.992,34.081 39.181,33.373C39.394,32.641 39.5,31.885 39.5,31.106C39.5,29.997 39.299,28.969 38.898,28.025C38.52,27.057 37.989,26.195 37.304,25.44H43.963Z"
+        android:fillColor="@color/educational_tip_card_logo_color_6"/>
+  </group>
+</vector>
diff --git a/chrome/browser/educational_tip/java/res/values-night/colors.xml b/chrome/browser/educational_tip/java/res/values-night/colors.xml
index ba08c89..04b8a41 100644
--- a/chrome/browser/educational_tip/java/res/values-night/colors.xml
+++ b/chrome/browser/educational_tip/java/res/values-night/colors.xml
@@ -15,4 +15,5 @@
   <color name="educational_tip_card_logo_color_7">@color/material_primary_70</color>
   <color name="educational_tip_card_logo_color_8">@color/material_secondary_40</color>
   <color name="educational_tip_card_logo_color_9">@color/material_tertiary_40</color>
+  <color name="educational_tip_card_logo_color_10">?attr/colorSurfaceContainer</color>
 </resources>
\ No newline at end of file
diff --git a/chrome/browser/educational_tip/java/res/values/colors.xml b/chrome/browser/educational_tip/java/res/values/colors.xml
index c230381cb71..99cb6fdf 100644
--- a/chrome/browser/educational_tip/java/res/values/colors.xml
+++ b/chrome/browser/educational_tip/java/res/values/colors.xml
@@ -15,4 +15,5 @@
   <color name="educational_tip_card_logo_color_7">@color/material_primary_60</color>
   <color name="educational_tip_card_logo_color_8">@color/material_secondary_80</color>
   <color name="educational_tip_card_logo_color_9">@color/material_tertiary_80</color>
+  <color name="educational_tip_card_logo_color_10">?attr/colorSurface</color>
 </resources>
\ No newline at end of file
diff --git a/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationTipModuleActionDelegate.java b/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationTipModuleActionDelegate.java
index 0d62ac8..e0d515c 100644
--- a/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationTipModuleActionDelegate.java
+++ b/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationTipModuleActionDelegate.java
@@ -45,6 +45,9 @@
     /** Opens the the history sync opt in page. */
     void showHistorySyncOptIn(Runnable removeModuleCallback);
 
+    /** Opens the settings page for the Tips Notifications channel. */
+    void showTipsNotificationsChannelSettings();
+
     /**
      * Returns the total number of tabs for relaunch across both regular and incognito browsing
      * modes through shared preference key.
diff --git a/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationalTipCardProviderFactory.java b/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationalTipCardProviderFactory.java
index 2a4e557..15974c3 100644
--- a/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationalTipCardProviderFactory.java
+++ b/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationalTipCardProviderFactory.java
@@ -12,6 +12,7 @@
 import org.chromium.chrome.browser.educational_tip.cards.QuickDeletePromoCoordinator;
 import org.chromium.chrome.browser.educational_tip.cards.TabGroupPromoCoordinator;
 import org.chromium.chrome.browser.educational_tip.cards.TabGroupSyncPromoCoordinator;
+import org.chromium.chrome.browser.educational_tip.cards.TipsNotificationsPromoCoordinator;
 import org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType;
 
 /** A factory interface for building a EducationalTipCardProvider instance. */
@@ -44,6 +45,9 @@
                         callbackController,
                         actionDelegate,
                         removeModuleCallback);
+            case ModuleType.TIPS_NOTIFICATIONS_PROMO:
+                return new TipsNotificationsPromoCoordinator(
+                        onModuleClickedCallback, callbackController, actionDelegate);
             default:
                 assert false : "Educational tip module's card type not supported!";
                 return null;
diff --git a/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationalTipCardProviderSignalHandler.java b/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationalTipCardProviderSignalHandler.java
index 1bf6bf85..1770156 100644
--- a/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationalTipCardProviderSignalHandler.java
+++ b/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationalTipCardProviderSignalHandler.java
@@ -73,6 +73,8 @@
                         "is_eligible_to_history_opt_in",
                         ProcessedValue.fromFloat(isEligibleToHistoryOptIn(profile)));
                 return inputContext;
+            case ModuleType.TIPS_NOTIFICATIONS_PROMO:
+                return inputContext;
             default:
                 assert false : "Card type not supported!";
                 return inputContext;
diff --git a/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationalTipModuleUtils.java b/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationalTipModuleUtils.java
index cbc363b..15133761 100644
--- a/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationalTipModuleUtils.java
+++ b/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/EducationalTipModuleUtils.java
@@ -21,6 +21,7 @@
         modules.add(ModuleType.TAB_GROUP_SYNC_PROMO);
         modules.add(ModuleType.QUICK_DELETE_PROMO);
         modules.add(ModuleType.HISTORY_SYNC_PROMO);
+        modules.add(ModuleType.TIPS_NOTIFICATIONS_PROMO);
         return modules;
     }
 }
diff --git a/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/cards/TipsNotificationsPromoCoordinator.java b/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/cards/TipsNotificationsPromoCoordinator.java
new file mode 100644
index 0000000..85bca37b
--- /dev/null
+++ b/chrome/browser/educational_tip/java/src/org/chromium/chrome/browser/educational_tip/cards/TipsNotificationsPromoCoordinator.java
@@ -0,0 +1,72 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.educational_tip.cards;
+
+import androidx.annotation.DrawableRes;
+
+import org.chromium.base.CallbackController;
+import org.chromium.build.annotations.NullMarked;
+import org.chromium.chrome.browser.educational_tip.EducationTipModuleActionDelegate;
+import org.chromium.chrome.browser.educational_tip.EducationalTipCardProvider;
+import org.chromium.chrome.browser.educational_tip.R;
+
+/** Coordinator for the Tips Notifications promo card. */
+@NullMarked
+public class TipsNotificationsPromoCoordinator implements EducationalTipCardProvider {
+    private final EducationTipModuleActionDelegate mActionDelegate;
+    private final Runnable mOnClickedRunnable;
+
+    /**
+     * @param onModuleClickedCallback The callback to be called when the module is clicked.
+     * @param callbackController The instance of {@link CallbackController}.
+     * @param actionDelegate The instance of {@link EducationTipModuleActionDelegate}.
+     */
+    public TipsNotificationsPromoCoordinator(
+            Runnable onModuleClickedCallback,
+            CallbackController callbackController,
+            EducationTipModuleActionDelegate actionDelegate) {
+        mActionDelegate = actionDelegate;
+
+        mOnClickedRunnable =
+                callbackController.makeCancelable(
+                        () -> {
+                            mActionDelegate.showTipsNotificationsChannelSettings();
+                            onModuleClickedCallback.run();
+                        });
+    }
+
+    // EducationalTipCardProvider implementation.
+
+    @Override
+    public String getCardTitle() {
+        return mActionDelegate
+                .getContext()
+                .getString(R.string.educational_tip_tips_notifications_title);
+    }
+
+    @Override
+    public String getCardDescription() {
+        return mActionDelegate
+                .getContext()
+                .getString(R.string.educational_tip_tips_notifications_description);
+    }
+
+    @Override
+    public String getCardButtonText() {
+        return mActionDelegate
+                .getContext()
+                .getString(R.string.educational_tip_tips_notifications_button);
+    }
+
+    @Override
+    public @DrawableRes int getCardImage() {
+        return R.drawable.tips_notifications_promo_logo;
+    }
+
+    @Override
+    public void onCardClicked() {
+        mOnClickedRunnable.run();
+    }
+}
diff --git a/chrome/browser/educational_tip/junit/src/org/chromium/chrome/browser/educational_tip/EducationalTipModuleMediatorUnitTest.java b/chrome/browser/educational_tip/junit/src/org/chromium/chrome/browser/educational_tip/EducationalTipModuleMediatorUnitTest.java
index b7f849b9..4d9b0d9 100644
--- a/chrome/browser/educational_tip/junit/src/org/chromium/chrome/browser/educational_tip/EducationalTipModuleMediatorUnitTest.java
+++ b/chrome/browser/educational_tip/junit/src/org/chromium/chrome/browser/educational_tip/EducationalTipModuleMediatorUnitTest.java
@@ -138,6 +138,13 @@
                 R.string.educational_tip_history_sync_title,
                 R.string.educational_tip_history_sync_description,
                 R.drawable.history_sync_promo_logo);
+
+        // Test showing tips notifications promo card.
+        testShowModuleImpl(
+                ModuleType.TIPS_NOTIFICATIONS_PROMO,
+                R.string.educational_tip_tips_notifications_title,
+                R.string.educational_tip_tips_notifications_description,
+                R.drawable.tips_notifications_promo_logo);
     }
 
     @Test
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_info.h b/chrome/browser/enterprise/connectors/analysis/content_analysis_info.h
index 09c669b..ce8472d1 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_info.h
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_info.h
@@ -35,7 +35,6 @@
   // Returns email of the active Gaia user based on the values provided by
   // `tab_url()` and `identity_manager()`. Only returns a value for Workspace
   // sites.
-  // TODO(crbug.com/415002299): Add tests for this.
   std::string GetContentAreaAccountEmail() const;
 };
 
diff --git a/chrome/browser/enterprise/connectors/analysis/source_destination_matcher_ash.cc b/chrome/browser/enterprise/connectors/analysis/source_destination_matcher_ash.cc
index 1e2e7503..547a69a 100644
--- a/chrome/browser/enterprise/connectors/analysis/source_destination_matcher_ash.cc
+++ b/chrome/browser/enterprise/connectors/analysis/source_destination_matcher_ash.cc
@@ -23,7 +23,6 @@
 // Checks if the key of the sources or destinations list is known.
 // This should only be extended when a key is properly supported.
 bool AllowedFSInfoKey(const std::string& key) {
-  // TODO(crbug.com/1340553): Also allow settings for app ids and smb.
   return key == "file_system_type";
 }
 
@@ -263,7 +262,6 @@
     ID* id,
     const base::Value::List* settings_list) {
   DCHECK(id);
-  // TODO(crbug.com/1340553): Adapt for app ids and smb settings
   if (!settings_list) {
     LOG(ERROR) << "No settings list found.";
     return;
diff --git a/chrome/browser/enterprise/connectors/connectors_service_browsertest.cc b/chrome/browser/enterprise/connectors/connectors_service_browsertest.cc
index 389a2dc..dce3711 100644
--- a/chrome/browser/enterprise/connectors/connectors_service_browsertest.cc
+++ b/chrome/browser/enterprise/connectors/connectors_service_browsertest.cc
@@ -703,8 +703,6 @@
 #endif
 }
 
-// TODO(b/302576851): Consider removing after EnableRelaxedAffiliationCheck
-// is cleaned up.
 IN_PROC_BROWSER_TEST_P(ConnectorsServiceAnalysisProfileBrowserTest,
                        Affiliation) {
   SetPrefs(AnalysisConnectorPref(BULK_DATA_ENTRY),
diff --git a/chrome/browser/enterprise/signin/managed_profile_creation_controller_browsertest.cc b/chrome/browser/enterprise/signin/managed_profile_creation_controller_browsertest.cc
index fd26ed0ec..8960230 100644
--- a/chrome/browser/enterprise/signin/managed_profile_creation_controller_browsertest.cc
+++ b/chrome/browser/enterprise/signin/managed_profile_creation_controller_browsertest.cc
@@ -20,12 +20,9 @@
 #include "components/signin/public/identity_manager/accounts_mutator.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/primary_account_mutator.h"
-#include "components/signin/public/identity_manager/signin_constants.h"
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using signin::constants::kNoHostedDomainFound;
-
 enum class ManagedProfileCreationResult {
   kNull,
   kExistingProfile,
@@ -350,17 +347,18 @@
         email, signin::SimpleAccountAvailabilityOptions{
                    .primary_account_consent_level = consent_level});
     // Fill the account info, in particular for the hosted_domain field.
-    account_info.full_name = "fullname";
-    account_info.given_name = "givenname";
-    account_info.hosted_domain = hosted_domain;
-    account_info.locale = "en";
-    account_info.picture_url = "https://example.com";
+    account_info = AccountInfo::Builder(account_info)
+                       .SetFullName("fullname")
+                       .SetGivenName("givenname")
+                       .SetHostedDomain(hosted_domain)
+                       .SetLocale("en")
+                       .SetAvatarUrl("https://example.com")
+                       .Build();
 
     AccountCapabilitiesTestMutator mutator(&account_info.capabilities);
-    mutator.set_is_subject_to_enterprise_features(hosted_domain !=
-                                                  kNoHostedDomainFound);
+    mutator.set_is_subject_to_enterprise_features(!hosted_domain.empty());
     mutator.set_is_subject_to_account_level_enterprise_policies(
-        hosted_domain != kNoHostedDomainFound);
+        !hosted_domain.empty());
 
     DCHECK(account_info.IsValid());
     identity_test_env()->UpdateAccountInfoForAccount(account_info);
diff --git a/chrome/browser/enterprise/signin/profile_management_disclaimer_service_browsertest.cc b/chrome/browser/enterprise/signin/profile_management_disclaimer_service_browsertest.cc
index a5a784c..3e90ff4e 100644
--- a/chrome/browser/enterprise/signin/profile_management_disclaimer_service_browsertest.cc
+++ b/chrome/browser/enterprise/signin/profile_management_disclaimer_service_browsertest.cc
@@ -32,12 +32,9 @@
 #include "components/signin/public/identity_manager/accounts_mutator.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/primary_account_mutator.h"
-#include "components/signin/public/identity_manager/signin_constants.h"
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using signin::constants::kNoHostedDomainFound;
-
 namespace {
 signin::IdentityManager* GetIdentityManager(Profile* profile) {
   return IdentityManagerFactory::GetForProfile(profile);
@@ -249,14 +246,16 @@
     AccountInfo account_info = identity_test_env()->MakePrimaryAccountAvailable(
         email, signin::ConsentLevel::kSignin);
     // Fill the account info, in particular for the hosted_domain field.
-    account_info.full_name = "fullname";
-    account_info.given_name = "givenname";
-    account_info.hosted_domain = hosted_domain;
-    account_info.locale = "en";
-    account_info.picture_url = "https://example.com";
+    account_info = AccountInfo::Builder(account_info)
+                       .SetFullName("fullname")
+                       .SetGivenName("givenname")
+                       .SetHostedDomain(hosted_domain)
+                       .SetLocale("en")
+                       .SetAvatarUrl("https://example.com")
+                       .Build();
 
     AccountCapabilitiesTestMutator mutator(&account_info.capabilities);
-    bool is_managed = hosted_domain != kNoHostedDomainFound;
+    bool is_managed = !hosted_domain.empty();
     mutator.set_is_subject_to_enterprise_features(is_managed);
 
     DCHECK(account_info.IsValid());
@@ -299,7 +298,7 @@
   AccountInfo primary_account_info =
       MakeValidPrimaryAccountInfoAvailableAndUpdate(
           "bob@example.com",
-          GetParam().is_managed ? "example.com" : kNoHostedDomainFound);
+          GetParam().is_managed ? "example.com" : std::string());
   base::RunLoop().RunUntilIdle();
   std::move(resetter).RunAndReset();
 
@@ -437,14 +436,16 @@
   AccountInfo MakeValidAccountInfoForAccount(AccountInfo&& account_info,
                                              const std::string& hosted_domain) {
     // Fill the account info, in particular for the hosted_domain field.
-    account_info.full_name = "fullname";
-    account_info.given_name = "givenname";
-    account_info.hosted_domain = hosted_domain;
-    account_info.locale = "en";
-    account_info.picture_url = "https://example.com";
+    account_info = AccountInfo::Builder(account_info)
+                       .SetFullName("fullname")
+                       .SetGivenName("givenname")
+                       .SetHostedDomain(hosted_domain)
+                       .SetLocale("en")
+                       .SetAvatarUrl("https://example.com")
+                       .Build();
 
     AccountCapabilitiesTestMutator mutator(&account_info.capabilities);
-    bool is_managed = hosted_domain != kNoHostedDomainFound;
+    bool is_managed = !hosted_domain.empty();
     mutator.set_is_subject_to_enterprise_features(is_managed);
 
     DCHECK(account_info.IsValid());
@@ -490,7 +491,7 @@
 
   primary_account_info = MakeValidAccountInfoForAccount(
       std::move(primary_account_info),
-      GetParam().is_managed ? "example.com" : kNoHostedDomainFound);
+      GetParam().is_managed ? "example.com" : std::string());
   ASSERT_TRUE(
       identity_manager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
   ASSERT_EQ(identity_manager()
@@ -610,14 +611,16 @@
                                              const std::string& hosted_domain) {
     AccountInfo account_info = identity_test_env()->MakeAccountAvailable(email);
     // Fill the account info, in particular for the hosted_domain field.
-    account_info.full_name = "fullname";
-    account_info.given_name = "givenname";
-    account_info.hosted_domain = hosted_domain;
-    account_info.locale = "en";
-    account_info.picture_url = "https://example.com";
+    account_info = AccountInfo::Builder(account_info)
+                       .SetFullName("fullname")
+                       .SetGivenName("givenname")
+                       .SetHostedDomain(hosted_domain)
+                       .SetLocale("en")
+                       .SetAvatarUrl("https://example.com")
+                       .Build();
 
     AccountCapabilitiesTestMutator mutator(&account_info.capabilities);
-    bool is_managed = hosted_domain != kNoHostedDomainFound;
+    bool is_managed = !hosted_domain.empty();
     mutator.set_is_subject_to_enterprise_features(is_managed);
 
     DCHECK(account_info.IsValid());
@@ -631,14 +634,16 @@
     AccountInfo account_info = identity_test_env()->MakePrimaryAccountAvailable(
         email, signin::ConsentLevel::kSignin);
     // Fill the account info, in particular for the hosted_domain field.
-    account_info.full_name = "fullname";
-    account_info.given_name = "givenname";
-    account_info.hosted_domain = hosted_domain;
-    account_info.locale = "en";
-    account_info.picture_url = "https://example.com";
+    account_info = AccountInfo::Builder(account_info)
+                       .SetFullName("fullname")
+                       .SetGivenName("givenname")
+                       .SetHostedDomain(hosted_domain)
+                       .SetLocale("en")
+                       .SetAvatarUrl("https://example.com")
+                       .Build();
 
     AccountCapabilitiesTestMutator mutator(&account_info.capabilities);
-    bool is_managed = hosted_domain != kNoHostedDomainFound;
+    bool is_managed = !hosted_domain.empty();
     mutator.set_is_subject_to_enterprise_features(is_managed);
 
     DCHECK(account_info.IsValid());
diff --git a/chrome/browser/extensions/api/chrome_extensions_api_client.cc b/chrome/browser/extensions/api/chrome_extensions_api_client.cc
index a1b0085d..ca3346399 100644
--- a/chrome/browser/extensions/api/chrome_extensions_api_client.cc
+++ b/chrome/browser/extensions/api/chrome_extensions_api_client.cc
@@ -29,6 +29,7 @@
 #include "chrome/browser/extensions/extension_action_runner.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/favicon/favicon_utils.h"
+#include "chrome/browser/supervised_user/supervised_user_extensions_delegate_impl.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
 #include "chrome/browser/ui/tabs/tab_list_interface.h"
@@ -412,6 +413,13 @@
   return new ChromeManagementAPIDelegate;
 }
 
+std::unique_ptr<SupervisedUserExtensionsDelegate>
+ChromeExtensionsAPIClient::CreateSupervisedUserExtensionsDelegate(
+    content::BrowserContext* browser_context) const {
+  return std::make_unique<SupervisedUserExtensionsDelegateImpl>(
+      browser_context);
+}
+
 MetricsPrivateDelegate* ChromeExtensionsAPIClient::GetMetricsPrivateDelegate() {
   if (!metrics_private_delegate_) {
     metrics_private_delegate_ =
diff --git a/chrome/browser/extensions/api/chrome_extensions_api_client_android.cc b/chrome/browser/extensions/api/chrome_extensions_api_client_android.cc
index f65a464..c9f24d0 100644
--- a/chrome/browser/extensions/api/chrome_extensions_api_client_android.cc
+++ b/chrome/browser/extensions/api/chrome_extensions_api_client_android.cc
@@ -7,7 +7,6 @@
 #include <memory>
 
 #include "base/notimplemented.h"
-#include "extensions/browser/supervised_user_extensions_delegate.h"
 #include "extensions/buildflags/buildflags.h"
 
 // TODO(crbug.com/417770773): This file contains stubs for the parts of
@@ -28,14 +27,6 @@
   return nullptr;
 }
 
-std::unique_ptr<SupervisedUserExtensionsDelegate>
-ChromeExtensionsAPIClient::CreateSupervisedUserExtensionsDelegate(
-    content::BrowserContext* browser_context) const {
-  // TODO(crbug.com/402488726): Support supervised users on desktop Android.
-  // This is a stub implementation that always blocks installs.
-  return std::make_unique<SupervisedUserExtensionsDelegate>();
-}
-
 std::unique_ptr<DisplayInfoProvider>
 ChromeExtensionsAPIClient::CreateDisplayInfoProvider() const {
   // TODO(crbug.com/417786011): Support display APIs on desktop Android.
diff --git a/chrome/browser/extensions/api/chrome_extensions_api_client_non_android.cc b/chrome/browser/extensions/api/chrome_extensions_api_client_non_android.cc
index 231673d..e0e3777 100644
--- a/chrome/browser/extensions/api/chrome_extensions_api_client_non_android.cc
+++ b/chrome/browser/extensions/api/chrome_extensions_api_client_non_android.cc
@@ -9,7 +9,6 @@
 #include "chrome/browser/extensions/api/chrome_extensions_api_client.h"
 #include "chrome/browser/extensions/system_display/display_info_provider.h"
 #include "chrome/browser/search/instant_service_factory.h"
-#include "chrome/browser/supervised_user/supervised_user_extensions_delegate_impl.h"
 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/api/system_display/display_info_provider.h"
@@ -30,13 +29,6 @@
   return std::make_unique<ChromeDevicePermissionsPrompt>(web_contents);
 }
 
-std::unique_ptr<SupervisedUserExtensionsDelegate>
-ChromeExtensionsAPIClient::CreateSupervisedUserExtensionsDelegate(
-    content::BrowserContext* browser_context) const {
-  return std::make_unique<SupervisedUserExtensionsDelegateImpl>(
-      browser_context);
-}
-
 std::unique_ptr<DisplayInfoProvider>
 ChromeExtensionsAPIClient::CreateDisplayInfoProvider() const {
   return CreateChromeDisplayInfoProvider();
diff --git a/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_apitest.cc b/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_apitest.cc
index 5809e1b..758e4726 100644
--- a/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_apitest.cc
+++ b/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_apitest.cc
@@ -174,7 +174,6 @@
   // This function implicitly expects the function to succeed (test failure
   // initiated otherwise).
   // Returns the value (NOT whether it had succeeded or failed).
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   template <typename Function>
   std::optional<base::Value> RunFunction(const base::Value::List& parameters) {
     scoped_refptr<Function> function(CreateFunction<Function>());
@@ -186,7 +185,6 @@
   // This function implicitly expects the function to succeed (test failure
   // initiated otherwise).
   // Returns the value (NOT whether it had succeeded or failed).
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   template <typename Function>
   std::optional<base::Value> RunNoArgsFunction() {
     base::Value::List params;
@@ -213,7 +211,6 @@
   // initiated otherwise).
   // Returns whether the function that was run returned a value, or avoided
   // returning a value, according to expectation.
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   bool StartLogging() {
     constexpr bool value_expected = false;
     std::optional<base::Value> value =
@@ -225,7 +222,6 @@
   // initiated otherwise).
   // Returns whether the function that was run returned a value, or avoided
   // returning a value, according to expectation.
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   bool StopLogging() {
     constexpr bool value_expected = false;
     std::optional<base::Value> value =
@@ -237,7 +233,6 @@
   // initiated otherwise).
   // Returns whether the function that was run returned a value, or avoided
   // returning a value, according to expectation.
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   bool DiscardLog() {
     constexpr bool value_expected = false;
     std::optional<base::Value> value =
@@ -249,7 +244,6 @@
   // initiated otherwise).
   // Returns whether the function that was run returned a value, or avoided
   // returning a value, according to expectation.
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   bool UploadLog(std::string* report_id) {
     constexpr bool value_expected = true;
     std::optional<base::Value> value =
@@ -265,7 +259,6 @@
   // initiated otherwise).
   // Returns whether the function that was run returned a value, or avoided
   // returning a value, according to expectation.
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   bool SetMetaData(const base::Value::List& data) {
     constexpr bool value_expected = false;
     std::optional<base::Value> value =
@@ -277,7 +270,6 @@
   // initiated otherwise).
   // Returns whether the function that was run returned a value, or avoided
   // returning a value, according to expectation.
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   bool StartRtpDump(bool incoming, bool outgoing) {
     base::Value::List params;
     AppendTabIdAndUrl(params);
@@ -293,7 +285,6 @@
   // initiated otherwise).
   // Returns whether the function that was run returned a value, or avoided
   // returning a value, according to expectation.
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   bool StopRtpDump(bool incoming, bool outgoing) {
     base::Value::List params;
     AppendTabIdAndUrl(params);
@@ -309,7 +300,6 @@
   // initiated otherwise).
   // Returns whether the function that was run returned a value, or avoided
   // returning a value, according to expectation.
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   bool StoreLog(const std::string& log_id) {
     base::Value::List params;
     AppendTabIdAndUrl(params);
@@ -324,7 +314,6 @@
   // initiated otherwise).
   // Returns whether the function that was run returned a value, or avoided
   // returning a value, according to expectation.
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   bool UploadStoredLog(const std::string& log_id, std::string* report_id) {
     base::Value::List params;
     AppendTabIdAndUrl(params);
@@ -343,7 +332,6 @@
   // initiated otherwise).
   // Returns whether the function that was run returned a value, or avoided
   // returning a value, according to expectation.
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   bool StartAudioDebugRecordings(int seconds) {
     base::Value::List params;
     AppendTabIdAndUrl(params);
@@ -359,7 +347,6 @@
   // initiated otherwise).
   // Returns whether the function that was run returned a value, or avoided
   // returning a value, according to expectation.
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   bool StopAudioDebugRecordings() {
     base::Value::List params;
     AppendTabIdAndUrl(params);
@@ -373,7 +360,6 @@
   // This function expects the function to succeed or fail according to
   // |expect_success| (test failure initiated otherwise). It also implicitly
   // expects that no value would be returned.
-  // TODO(crbug.com/41381060): Return success/failure of the executed function.
   void StartEventLogging(const std::string& session_id,
                          int max_log_size_bytes,
                          int output_period_ms,
diff --git a/chrome/browser/extensions/api/webstore_private/extension_install_status.cc b/chrome/browser/extensions/api/webstore_private/extension_install_status.cc
index edc9f0c..5550c9f8 100644
--- a/chrome/browser/extensions/api/webstore_private/extension_install_status.cc
+++ b/chrome/browser/extensions/api/webstore_private/extension_install_status.cc
@@ -60,8 +60,6 @@
 
   // Extension is allowed by wildcard or update_url, checks required permissions
   // and manifest type.
-  // TODO(crbug.com/40133205): Find out the right way to handle extension policy
-  // priority.
   if (manifest_type != Manifest::Type::TYPE_UNKNOWN &&
       !extension_management->IsAllowedManifestType(manifest_type,
                                                    extension_id)) {
diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
index 20702b1..a9891e58 100644
--- a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
+++ b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
@@ -57,6 +57,7 @@
 #include "content/public/browser/gpu_feature_checker.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
+#include "extensions/browser/api/management/management_api.h"
 #include "extensions/browser/extension_dialog_auto_confirm.h"
 #include "extensions/browser/extension_function_constants.h"
 #include "extensions/browser/extension_registry.h"
@@ -75,10 +76,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
 
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-#include "extensions/browser/api/management/management_api.h"
-#endif
-
 #if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #endif
@@ -572,7 +569,6 @@
 
 void WebstorePrivateBeginInstallWithManifest3Function::RequestExtensionApproval(
     content::WebContents* web_contents) {
-#if BUILDFLAG(ENABLE_EXTENSIONS)
   SupervisedUserExtensionsDelegate* supervised_user_extensions_delegate =
       ManagementAPI::GetFactoryInstance()
           ->Get(profile_)
@@ -586,12 +582,6 @@
       *dummy_extension_, web_contents,
       gfx::ImageSkia::CreateFrom1xBitmap(icon_),
       std::move(extension_approval_callback));
-#else
-  // TODO(crbug.com/410616937): Support supervised user install controls on
-  // desktop Android.
-  NOTIMPLEMENTED() << "Supervised user checks not yet supported on Android.";
-  OnExtensionApprovalDone(SupervisedExtensionApprovalResult::kApproved);
-#endif
 }
 
 void WebstorePrivateBeginInstallWithManifest3Function::OnExtensionApprovalDone(
@@ -615,18 +605,12 @@
 
 void WebstorePrivateBeginInstallWithManifest3Function::
     OnExtensionApprovalApproved() {
-#if BUILDFLAG(ENABLE_EXTENSIONS)
   SupervisedUserExtensionsDelegate* supervised_user_extensions_delegate =
       ManagementAPI::GetFactoryInstance()
           ->Get(profile_)
           ->GetSupervisedUserExtensionsDelegate();
   CHECK(supervised_user_extensions_delegate);
   supervised_user_extensions_delegate->AddExtensionApproval(*dummy_extension_);
-#else
-  // TODO(crbug.com/410616937): Support supervised user install controls on
-  // desktop Android.
-  NOTIMPLEMENTED() << "Supervised user checks not yet supported on Android.";
-#endif
 
   HandleInstallProceed();
 }
diff --git a/chrome/browser/extensions/web_accessible_resources_browsertest.cc b/chrome/browser/extensions/web_accessible_resources_browsertest.cc
index 9b1cec5b..a60a64b5 100644
--- a/chrome/browser/extensions/web_accessible_resources_browsertest.cc
+++ b/chrome/browser/extensions/web_accessible_resources_browsertest.cc
@@ -474,12 +474,11 @@
   EXPECT_FALSE(observer.last_navigation_succeeded());
   EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, observer.last_net_error_code());
 }
+#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
 // DNR, WAR, and use_dynamic_url with the extension feature. DNR does not
 // currently succeed when redirecting to a resource using use_dynamic_url with
 // query parameters.
-// TODO(crbug.com/383366125): Port to desktop Android once chrome.runtime is
-// fully ported. Right now the ExtensionTestMessageListener times out.
 IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesBrowserTest,
                        DeclarativeNetRequest) {
   ExtensionTestMessageListener listener("ready");
@@ -494,7 +493,7 @@
     content::WebContents* web_contents = GetActiveWebContents();
     GURL gurl = embedded_test_server()->GetURL("example.com", "/simple.html");
     content::TestNavigationObserver navigation_observer(web_contents);
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl));
+    ASSERT_TRUE(NavigateToURL(web_contents, gurl));
     ASSERT_TRUE(navigation_observer.last_navigation_succeeded());
     EXPECT_EQ(gurl, web_contents->GetLastCommittedURL());
   }
@@ -527,7 +526,6 @@
     EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
   }
 }
-#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
 // Verify setting script.src from a content script that relies on web request to
 // redirect to a web accessible resource. It's important to set `script.src`
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 2fa683f6..e65aba0 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -5305,8 +5305,7 @@
     "name": "fluent-overlay-scrollbars",
     "owners": [
       "gastonr@microsoft.com",
-      "gerchiko@microsoft.com",
-      "yshalivskyy@microsoft.com"
+      "gerchiko@microsoft.com"
     ],
     "expiry_milestone": 150
   },
@@ -5314,8 +5313,7 @@
     "name": "fluent-scrollbars",
     "owners": [
       "gastonr@microsoft.com",
-      "gerchiko@microsoft.com",
-      "yshalivskyy@microsoft.com"
+      "gerchiko@microsoft.com"
     ],
     "expiry_milestone": 150
   },
@@ -6615,6 +6613,14 @@
     "expiry_milestone": 145
   },
   {
+    "name": "lens-omnient-shader-v2-enabled",
+    "owners": [
+      "aboodmufti@google.com",
+      "cmyang@google.com"
+    ],
+    "expiry_milestone": 147
+  },
+  {
     "name": "lens-overlay-alternative-onboarding",
     "owners": [ "stkhapugin@chromium.org", "christianxu@chromium.org", "lens-chrome@google.com" ],
     "expiry_milestone": 140
@@ -9882,6 +9888,11 @@
     "expiry_milestone": 150
   },
   {
+    "name": "tab-groups-focusing",
+    "owners": [ "dpenning@google.com", "top-chrome-desktop-ui@google.com" ],
+    "expiry_milestone": 150
+  },
+  {
     "name": "tab-groups-on-ipad",
     "owners": [ "alionadangla@chromium.org", "lpromero@chromium.org", "bling-flags@google.com" ],
     "expiry_milestone": 137
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 16dfea1d..422f012 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -3899,6 +3899,11 @@
     "Set the relative alignment between the toolbar height side panel and the "
     "content height side panel";
 
+inline constexpr char kTabGroupsFocusingName[] = "Tab Groups Focusing";
+inline constexpr char kTabGroupsFocusingDescription[] =
+    "When a tab group is focused, the tabstrip constrains visiblity to the "
+    "tabs in that group.";
+
 inline constexpr char kTabStorageSqlitePrototypeName[] =
     "Tab Storage SQLite Prototype";
 inline constexpr char kTabStorageSqlitePrototypeDescription[] =
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 839b0898..83818b75 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
@@ -796,7 +796,7 @@
     public static final CachedFlag sAndroidWebAppMenuButton =
             newCachedFlag(ANDROID_WEB_APP_MENU_BUTTON, false);
     public static final CachedFlag sAndroidWindowControlsOverlay =
-            newCachedFlag(ANDROID_WINDOW_CONTROLS_OVERLAY, false);
+            newCachedFlag(ANDROID_WINDOW_CONTROLS_OVERLAY, true);
     public static final CachedFlag sAndroidWindowManagementWebApi =
             newCachedFlag(
                     ANDROID_WINDOW_MANAGEMENT_WEB_API,
diff --git a/chrome/browser/glic/browser_ui/context_sharing_border_view_interactive_uitest.cc b/chrome/browser/glic/browser_ui/context_sharing_border_view_interactive_uitest.cc
index cf77eea..bf48ca3 100644
--- a/chrome/browser/glic/browser_ui/context_sharing_border_view_interactive_uitest.cc
+++ b/chrome/browser/glic/browser_ui/context_sharing_border_view_interactive_uitest.cc
@@ -55,13 +55,6 @@
 #include "base/mac/mac_util.h"
 #endif  // BUILDFLAG(IS_MAC)
 
-// TODO(crbug.com/460522797): Re-enable failing tests on ChromeOS.
-#if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE(test_name) DISABLED_##test_name
-#else
-#define MAYBE(test_name) test_name
-#endif
-
 namespace glic {
 
 namespace {
@@ -223,16 +216,25 @@
   }
 };
 
-class ContextSharingBorderViewUiTest : public test::InteractiveGlicTest {
+class ContextSharingBorderViewUiTestBase : public test::InteractiveGlicTest {
  public:
-  ContextSharingBorderViewUiTest() {
+  explicit ContextSharingBorderViewUiTestBase(bool enable_gpu_rasterization) {
+    std::string enabled_features;
+    // These features disable animation, so disable them here.
+    std::string disabled_features =
+        "GlicForceSimplifiedBorder,GlicForceNonSkSLBorder";
+
     // Toggling UiGpuRasterization is only possible via command line.
-    features_.InitFromCommandLine(
-        "UiGpuRasterization",
-        // These features disable animation, so disable them here.
-        "GlicForceSimplifiedBorder,GlicForceNonSkSLBorder");
+    if (enable_gpu_rasterization) {
+      enabled_features = "UiGpuRasterization";
+    } else {
+      disabled_features += ",UiGpuRasterization";
+    }
+
+    features_.InitFromCommandLine(enabled_features, disabled_features);
   }
-  ~ContextSharingBorderViewUiTest() override = default;
+
+  ~ContextSharingBorderViewUiTestBase() override = default;
 
   void SetUpOnMainThread() override {
     embedded_test_server()->ServeFilesFromSourceDirectory(
@@ -243,6 +245,12 @@
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     command_line->AppendSwitch(switches::kForcePrefersNoReducedMotion);
+
+    // This ensures that gpu rasterization (i.e hardware acceleration )is
+    // available regardless of device. (This is required for`
+    // ContextSharingBorderView` to animate - See
+    // `AnimatedEffectView::ForceSimplifiedShader()`)
+    command_line->AppendSwitch(switches::kIgnoreGpuBlocklist);
     test::InteractiveGlicTest::SetUpCommandLine(command_line);
   }
 
@@ -293,6 +301,15 @@
   base::test::ScopedFeatureList features_;
   TestFactory test_factory_;
 };
+
+class ContextSharingBorderViewUiTest
+    : public ContextSharingBorderViewUiTestBase {
+ public:
+  ContextSharingBorderViewUiTest()
+      : ContextSharingBorderViewUiTestBase(/*enable_gpu_rasterization=*/true) {}
+  ~ContextSharingBorderViewUiTest() override = default;
+};
+
 }  // namespace
 
 // Exercise that, the border is resized correctly whenever the browser's size
@@ -368,7 +385,7 @@
 
 // Exercise the default user journey: toggles the border animation and wait for
 // it to finish.
-IN_PROC_BROWSER_TEST_F(ContextSharingBorderViewUiTest, MAYBE(SmokeTest)) {
+IN_PROC_BROWSER_TEST_F(ContextSharingBorderViewUiTest, SmokeTest) {
   auto* border = browser()
                      ->window()
                      ->AsBrowserView()
@@ -500,8 +517,7 @@
 }
 
 // Ensures that the emphasis animation is restarted when tab focus changes.
-IN_PROC_BROWSER_TEST_F(ContextSharingBorderViewUiTest,
-                       MAYBE(FocusedTabChange)) {
+IN_PROC_BROWSER_TEST_F(ContextSharingBorderViewUiTest, FocusedTabChange) {
   if (base::FeatureList::IsEnabled(features::kGlicMultiInstance)) {
     // TODO(b/453696965): Broken in multi-instance.
     GTEST_SKIP() << "Skipping for kGlicMultiInstance";
@@ -563,8 +579,7 @@
 
 // Ensures that only the emphasis animation is restarted when the focused tab is
 // destroyed.
-IN_PROC_BROWSER_TEST_F(ContextSharingBorderViewUiTest,
-                       MAYBE(FocusedTabDestroyed)) {
+IN_PROC_BROWSER_TEST_F(ContextSharingBorderViewUiTest, FocusedTabDestroyed) {
   if (base::FeatureList::IsEnabled(features::kGlicMultiInstance)) {
     // TODO(b/453696965): Broken in multi-instance.
     GTEST_SKIP() << "Skipping for kGlicMultiInstance";
@@ -632,8 +647,7 @@
 
 // TODO(crbug.com/430097333): Wayland doesn't support programmatic window
 // activation. Re-enable when activation is supported.
-// TODO(crbug.com/460522797): Enable on ChromeOS.
-#if BUILDFLAG(IS_OZONE_WAYLAND) || BUILDFLAG(IS_CHROMEOS)
+#if BUILDFLAG(IS_OZONE_WAYLAND)
 #define MAYBE_FocusedWindowChange DISABLED_FocusedWindowChange
 #else
 #define MAYBE_FocusedWindowChange FocusedWindowChange
@@ -715,7 +729,7 @@
 // Ensures that the border fades out before disappearing entirely during
 // emphasis ramp up.
 IN_PROC_BROWSER_TEST_F(ContextSharingBorderViewUiTest,
-                       MAYBE(RampingDownDuringEmphasisRampUp)) {
+                       RampingDownDuringEmphasisRampUp) {
   auto* border = browser()
                      ->window()
                      ->AsBrowserView()
@@ -770,7 +784,7 @@
 // Ensures that the border fades out before disappearing entirely during opacity
 // ramp up.
 IN_PROC_BROWSER_TEST_F(ContextSharingBorderViewUiTest,
-                       MAYBE(RampingDownDuringOpacityRampUp)) {
+                       RampingDownDuringOpacityRampUp) {
   auto* border = browser()
                      ->window()
                      ->AsBrowserView()
@@ -876,7 +890,7 @@
   EXPECT_FALSE(border->IsShowing());
 }
 
-IN_PROC_BROWSER_TEST_F(ContextSharingBorderViewUiTest, MAYBE(EnsureTimeWraps)) {
+IN_PROC_BROWSER_TEST_F(ContextSharingBorderViewUiTest, EnsureTimeWraps) {
   auto* border = browser()
                      ->window()
                      ->AsBrowserView()
@@ -1239,25 +1253,19 @@
 
 namespace {
 class ContextSharingBorderViewWithoutHardwareAccelerationUiTest
-    : public ContextSharingBorderViewUiTest {
+    : public ContextSharingBorderViewUiTestBase {
  public:
-  ContextSharingBorderViewWithoutHardwareAccelerationUiTest() = default;
+  ContextSharingBorderViewWithoutHardwareAccelerationUiTest()
+      : ContextSharingBorderViewUiTestBase(/*enable_gpu_rasterization=*/false) {
+  }
   ~ContextSharingBorderViewWithoutHardwareAccelerationUiTest() override =
       default;
-
-  void SetUp() override {
-    UseSoftwareCompositing();
-    test::InteractiveGlicTest::SetUp();
-  }
 };
 }  // namespace
 
 // Ensures that when there is no hardware acceleration, the emphasis animation
 // is skipped and we just show an opacity ramp up and ramp down animation.
 // Note: Ramp up and ramp down duration in this case is 200ms.
-// Secondary Note: ChromeOS requires hardware acceleration, so this test doesn't
-// make sense on that platform.
-#if !BUILDFLAG(IS_CHROMEOS)
 IN_PROC_BROWSER_TEST_F(
     ContextSharingBorderViewWithoutHardwareAccelerationUiTest,
     BasicRampingUpAndDown) {
@@ -1320,7 +1328,6 @@
   EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
   EXPECT_FALSE(border->IsShowing());
 }
-#endif  // !BUILDFLAG(IS_CHROMEOS)
 
 // Regression test for crbug.com/409649143. Ensure we clear the "start ramp down
 // state" if StopShowing is called immediately after starting the ramp down.
diff --git a/chrome/browser/glic/fre/glic_fre_controller_interactive_uitest.cc b/chrome/browser/glic/fre/glic_fre_controller_interactive_uitest.cc
index cb0ac6f..e71f0b4 100644
--- a/chrome/browser/glic/fre/glic_fre_controller_interactive_uitest.cc
+++ b/chrome/browser/glic/fre/glic_fre_controller_interactive_uitest.cc
@@ -300,8 +300,11 @@
       }));
 }
 
+// Note: ChromeOS maintains auth tokens as a part of OS User sessions,
+// and so reauth flow is not expected.
+// TODO(crbug.com/450629835): Revisit if we figure out actual flow we need
+// reauth.
 #if !BUILDFLAG(IS_CHROMEOS)
-// TODO(crbug.com/460829871): Evaluate whether this is relevant on ChromeOS.
 IN_PROC_BROWSER_TEST_F(GlicFreControllerUiTest,
                        InvalidatedAccountSignInOnGlicFreOpenFlow) {
   auto server_running = fre_server().StartAcceptingConnectionsAndReturnHandle();
diff --git a/chrome/browser/glic/glic_keyed_service_browsertest.cc b/chrome/browser/glic/glic_keyed_service_browsertest.cc
index 3de6fbf..1d4a8443 100644
--- a/chrome/browser/glic/glic_keyed_service_browsertest.cc
+++ b/chrome/browser/glic/glic_keyed_service_browsertest.cc
@@ -25,6 +25,10 @@
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chromeos/constants/chromeos_features.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 namespace {
 // The maximum number of times the closing toast should be shown for a profile.
 constexpr int kToastShownMax = 2;
@@ -35,9 +39,15 @@
     scoped_feature_list_.InitWithFeaturesAndParameters(
         // Both these features are required for the glic service to be properly
         // created.
-        {{features::kGlic, {}},
-         {features::kTabstripComboButton, {}},
-         {features::kGlicActorUi, {{features::kGlicActorUiToastName, "true"}}}},
+        {
+            {features::kGlic, {}},
+            {features::kTabstripComboButton, {}},
+            {features::kGlicActorUi,
+             {{features::kGlicActorUiToastName, "true"}}},
+#if BUILDFLAG(IS_CHROMEOS)
+            {chromeos::features::kFeatureManagementGlic, {}},
+#endif  // BUILDFLAG(IS_CHROMEOS)
+        },
         /*disabled_features*/ {});
   }
 
diff --git a/chrome/browser/glic/host/glic_annotation_manager.cc b/chrome/browser/glic/host/glic_annotation_manager.cc
index 83ce4b5..aa320b4d 100644
--- a/chrome/browser/glic/host/glic_annotation_manager.cc
+++ b/chrome/browser/glic/host/glic_annotation_manager.cc
@@ -90,7 +90,6 @@
     return base::unexpected(mojom::ScrollToErrorReason::kNoMatchingDocument);
   }
 
-  // TODO(crbug.com/422728758): Implement url verification for PDFs.
   return &pdf_helper->render_frame_host();
 #else
   return base::unexpected(mojom::ScrollToErrorReason::kNotSupported);
diff --git a/chrome/browser/glic/public/glic_enabling.cc b/chrome/browser/glic/public/glic_enabling.cc
index 06d4e7e..2f4e044 100644
--- a/chrome/browser/glic/public/glic_enabling.cc
+++ b/chrome/browser/glic/public/glic_enabling.cc
@@ -34,6 +34,7 @@
 #if BUILDFLAG(IS_CHROMEOS)
 #include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"  // nogncheck
 #include "chromeos/ash/components/browser_context_helper/browser_context_types.h"  // nogncheck
+#include "chromeos/constants/chromeos_features.h"
 #include "components/user_manager/user.h"       // nogncheck
 #include "components/user_manager/user_type.h"  // nogncheck
 #endif
@@ -143,9 +144,13 @@
 }
 
 bool GlicEnabling::IsEnabledByFlags() {
-  // Check that the feature flags are enabled.
-  return base::FeatureList::IsEnabled(features::kGlic) &&
-         features::HasTabSearchToolbarButton();
+  bool is_enabled = base::FeatureList::IsEnabled(features::kGlic) &&
+                    features::HasTabSearchToolbarButton();
+#if BUILDFLAG(IS_CHROMEOS)
+  is_enabled = is_enabled && base::FeatureList::IsEnabled(
+                                 chromeos::features::kFeatureManagementGlic);
+#endif  // BUILDFLAG(IS_CHROMEOS)
+  return is_enabled;
 }
 
 bool GlicEnabling::IsProfileEligible(const Profile* profile) {
diff --git a/chrome/browser/glic/public/glic_enabling_browsertest.cc b/chrome/browser/glic/public/glic_enabling_browsertest.cc
index 0c1bd10..5e71ed59 100644
--- a/chrome/browser/glic/public/glic_enabling_browsertest.cc
+++ b/chrome/browser/glic/public/glic_enabling_browsertest.cc
@@ -28,6 +28,10 @@
 #include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
 #include "third_party/metrics_proto/system_profile.pb.h"
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chromeos/constants/chromeos_features.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 using base::test::FeatureRef;
 
 namespace glic {
@@ -48,8 +52,14 @@
  protected:
   virtual void InitializeFeatureList() {
     scoped_feature_list_.InitWithFeatures(
-        {features::kGlic, features::kTabstripComboButton,
-         features::kGlicRollout},
+        {
+            features::kGlic,
+            features::kTabstripComboButton,
+            features::kGlicRollout,
+#if BUILDFLAG(IS_CHROMEOS)
+            chromeos::features::kFeatureManagementGlic,
+#endif  // BUILDFLAG(IS_CHROMEOS)
+        },
         {});
   }
 
@@ -93,8 +103,14 @@
  public:
   void InitializeFeatureList() override {
     scoped_feature_list_.InitWithFeatures(
-        {features::kGlic, features::kTabstripComboButton,
-         features::kGlicTieredRollout},
+        {
+            features::kGlic,
+            features::kTabstripComboButton,
+            features::kGlicTieredRollout,
+#if BUILDFLAG(IS_CHROMEOS)
+            chromeos::features::kFeatureManagementGlic,
+#endif  // BUILDFLAG(IS_CHROMEOS)
+        },
         {features::kGlicRollout});
   }
   ~GlicEnablingTieredRolloutTest() override = default;
@@ -154,8 +170,16 @@
  public:
   void InitializeFeatureList() override {
     scoped_feature_list_.InitWithFeatures(
-        {features::kGlic, features::kTabstripComboButton,
-         features::kGlicTieredRollout, features::kGlicRollout},
+        {
+            features::kGlic,
+            features::kTabstripComboButton,
+            features::kGlicTieredRollout,
+            features::kGlicRollout,
+#if BUILDFLAG(IS_CHROMEOS)
+            chromeos::features::kFeatureManagementGlic,
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
+        },
         {});
   }
   ~GlicEnablingSimultaneousRolloutTest() override = default;
diff --git a/chrome/browser/glic/public/glic_enabling_unittest.cc b/chrome/browser/glic/public/glic_enabling_unittest.cc
index efc65968..e68d086 100644
--- a/chrome/browser/glic/public/glic_enabling_unittest.cc
+++ b/chrome/browser/glic/public/glic_enabling_unittest.cc
@@ -18,6 +18,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS)
 #include "chrome/browser/ash/test/glic_user_session_test_helper.h"
+#include "chromeos/constants/chromeos_features.h"
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
 using base::test::FeatureRef;
@@ -32,7 +33,14 @@
 
     // Enable kGlic and kTabstripComboButton by default for testing.
     scoped_feature_list_.InitWithFeatures(
-        {features::kGlic, features::kTabstripComboButton}, {});
+        {
+            features::kGlic,
+            features::kTabstripComboButton,
+#if BUILDFLAG(IS_CHROMEOS)
+            chromeos::features::kFeatureManagementGlic,
+#endif  // BUILDFLAG(IS_CHROMEOS)
+        },
+        {});
   }
 
   void TearDown() override {
@@ -69,7 +77,14 @@
  public:
   GlicEnablingProfileEligibilityTest() {
     scoped_feature_list_.InitWithFeatures(
-        /*enabled_features=*/{features::kGlic, features::kTabstripComboButton},
+        /*enabled_features=*/
+        {
+            features::kGlic,
+            features::kTabstripComboButton,
+#if BUILDFLAG(IS_CHROMEOS)
+            chromeos::features::kFeatureManagementGlic,
+#endif  // BUILDFLAG(IS_CHROMEOS)
+        },
         /*disabled_features=*/{});
   }
   ~GlicEnablingProfileEligibilityTest() override = default;
diff --git a/chrome/browser/glic/test_support/glic_test_environment.cc b/chrome/browser/glic/test_support/glic_test_environment.cc
index 5025542..451e26c7 100644
--- a/chrome/browser/glic/test_support/glic_test_environment.cc
+++ b/chrome/browser/glic/test_support/glic_test_environment.cc
@@ -20,7 +20,8 @@
 
 #if BUILDFLAG(IS_CHROMEOS)
 #include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
-#endif
+#include "chromeos/constants/chromeos_features.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 namespace glic {
 
@@ -111,7 +112,11 @@
 
 std::vector<base::test::FeatureRef> GetDefaultEnabledGlicTestFeatures() {
   return {features::kGlic, features::kTabstripComboButton,
-          features::kGlicRollout};
+          features::kGlicRollout,
+#if BUILDFLAG(IS_CHROMEOS)
+          chromeos::features::kFeatureManagementGlic
+#endif  // BUILDFLAG(IS_CHROMEOS)
+  };
 }
 std::vector<base::test::FeatureRef> GetDefaultDisabledGlicTestFeatures() {
   return {features::kGlicWarming, features::kGlicFreWarming};
diff --git a/chrome/browser/glic/test_support/interactive_glic_test.h b/chrome/browser/glic/test_support/interactive_glic_test.h
index ecebddda..36b40e12 100644
--- a/chrome/browser/glic/test_support/interactive_glic_test.h
+++ b/chrome/browser/glic/test_support/interactive_glic_test.h
@@ -67,6 +67,10 @@
 #include "url/gurl.h"
 #include "url/url_util.h"
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chromeos/constants/chromeos_features.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 namespace glic {
 class GlicWindowControllerImpl;
 }
@@ -133,7 +137,12 @@
         {{features::kGlic, glic_params},
          {features::kTabstripComboButton, {}},
          {features::kGlicRollout, {}},
-         {features::kGlicKeyboardShortcutNewBadge, {}}},
+         {features::kGlicKeyboardShortcutNewBadge, {}},
+#if BUILDFLAG(IS_CHROMEOS)
+         { chromeos::features::kFeatureManagementGlic,
+           {} }
+#endif  // BUILDFLAG(IS_CHROMEOS)
+        },
         {});
   }
 
diff --git a/chrome/browser/glic/test_support/non_interactive_glic_test.cc b/chrome/browser/glic/test_support/non_interactive_glic_test.cc
index b1f9b76..e9b2391 100644
--- a/chrome/browser/glic/test_support/non_interactive_glic_test.cc
+++ b/chrome/browser/glic/test_support/non_interactive_glic_test.cc
@@ -6,9 +6,17 @@
 
 #include "chrome/browser/glic/host/context/glic_focused_browser_manager.h"
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chromeos/constants/chromeos_features.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 namespace glic {
 
 NonInteractiveGlicTest::NonInteractiveGlicTest() {
+#if BUILDFLAG(IS_CHROMEOS)
+  features_.InitAndEnableFeature(chromeos::features::kFeatureManagementGlic);
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
   GlicFocusedBrowserManager::SetTestingModeForTesting(true);
 }
 
diff --git a/chrome/browser/glic/test_support/non_interactive_glic_test.h b/chrome/browser/glic/test_support/non_interactive_glic_test.h
index 0c97eb6..85ececd 100644
--- a/chrome/browser/glic/test_support/non_interactive_glic_test.h
+++ b/chrome/browser/glic/test_support/non_interactive_glic_test.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_GLIC_TEST_SUPPORT_NON_INTERACTIVE_GLIC_TEST_H_
 #define CHROME_BROWSER_GLIC_TEST_SUPPORT_NON_INTERACTIVE_GLIC_TEST_H_
 
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/glic/test_support/glic_test_util.h"
 #include "chrome/browser/glic/test_support/interactive_glic_test.h"
 
@@ -28,6 +29,7 @@
   BrowserActivator& browser_activator() { return browser_activator_; }
 
  private:
+  base::test::ScopedFeatureList features_;
   BrowserActivator browser_activator_;
 };
 
diff --git a/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc b/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc
index d58de2f2..f0c93552 100644
--- a/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc
+++ b/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc
@@ -59,7 +59,10 @@
 namespace glic {
 
 namespace {
+
+#if !BUILDFLAG(IS_CHROMEOS)
 DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kFirstTab);
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 
 const InteractiveBrowserTestApi::DeepQuery kMockGlicClientHangButton = {
     "#hang"};
@@ -461,16 +464,13 @@
       }));
 }
 
-// TODO(crbug.com/460831500): Enable on ChromeOS.
-#if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE_InvalidatedAccountWhileLoadingGlic \
-  DISABLED_InvalidatedAccountWhileLoadingGlic
-#else
-#define MAYBE_InvalidatedAccountWhileLoadingGlic \
-  InvalidatedAccountWhileLoadingGlic
-#endif
+// Note: ChromeOS maintains account auth as a part of OS User session.
+// So invalidation is not supported.
+// TODO(crbug.com/450629835): Revisit if we figure out actual flow we need
+// reauth.
+#if !BUILDFLAG(IS_CHROMEOS)
 IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest,
-                       MAYBE_InvalidatedAccountWhileLoadingGlic) {
+                       InvalidatedAccountWhileLoadingGlic) {
   if (base::FeatureList::IsEnabled(features::kGlicMultiInstance)) {
     // TODO(b/453696965): Broken in multi-instance.
     GTEST_SKIP() << "Skipping for kGlicMultiInstance";
@@ -490,16 +490,8 @@
       WaitForWebUIState(mojom::WebUiState::kReady));
 }
 
-// TODO(crbug.com/460831500): Enable on ChromeOS.
-#if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE_InvalidatedAccountSignInOnGlicOpenFlow \
-  DISABLED_InvalidatedAccountSignInOnGlicOpenFlow
-#else
-#define MAYBE_InvalidatedAccountSignInOnGlicOpenFlow \
-  InvalidatedAccountSignInOnGlicOpenFlow
-#endif
 IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest,
-                       MAYBE_InvalidatedAccountSignInOnGlicOpenFlow) {
+                       InvalidatedAccountSignInOnGlicOpenFlow) {
   if (base::FeatureList::IsEnabled(features::kGlicMultiInstance)) {
     // TODO(b/453696965): Broken in multi-instance, requirements have changed.
     // Update this test.
@@ -515,6 +507,7 @@
                   WaitForAndInstrumentGlic(kHostOnly),
                   WaitForWebUIState(mojom::WebUiState::kReady));
 }
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 
 IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest,
                        AccountInvalidatedWhileGlicOpen) {
@@ -762,14 +755,11 @@
 }
 #endif  // BUILDFLAG(IS_MAC)
 
-// TODO(crbug.com/460831500): Enable on ChromeOS.
-#if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE_PermanentlyDeleteProfile DISABLED_PermanentlyDeleteProfile
-#else
-#define MAYBE_PermanentlyDeleteProfile PermanentlyDeleteProfile
-#endif
-IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest,
-                       MAYBE_PermanentlyDeleteProfile) {
+// Note: ChromeOS maintains account auth as a part of OS User session,
+// and Profile is coupled with the User. Thus, deletion Profile
+// during the use should not happen.
+#if !BUILDFLAG(IS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest, PermanentlyDeleteProfile) {
   if (base::FeatureList::IsEnabled(features::kGlicMultiInstance)) {
     // TODO(b/453696965): Broken in multi-instance.
     GTEST_SKIP() << "Skipping for kGlicMultiInstance";
@@ -797,6 +787,7 @@
 
   EXPECT_FALSE(service1->IsWindowShowing());
 }
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 
 class GlicWindowControllerWithPreviousPostionUiTest
     : public GlicWindowControllerUiTest {
diff --git a/chrome/browser/google/google_update_policy_fetcher_win.cc b/chrome/browser/google/google_update_policy_fetcher_win.cc
index aa76d49f..853f8fc 100644
--- a/chrome/browser/google/google_update_policy_fetcher_win.cc
+++ b/chrome/browser/google/google_update_policy_fetcher_win.cc
@@ -29,7 +29,6 @@
 
 namespace {
 
-// TODO(crbug.com/40271852): Add unit tests for these GoogleUpdate policies.
 constexpr char kAutoUpdateCheckPeriodMinutes[] = "AutoUpdateCheckPeriodMinutes";
 constexpr char kDownloadPreference[] = "DownloadPreference";
 constexpr char kForceInstallApps[] = "ForceInstallApps";
diff --git a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubActionButtonMediator.java b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubActionButtonMediator.java
index 73459717..b3414c3 100644
--- a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubActionButtonMediator.java
+++ b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubActionButtonMediator.java
@@ -9,7 +9,6 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplier;
-import org.chromium.base.supplier.TransitiveObservableSupplier;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -22,7 +21,7 @@
 
     private final Callback<FullButtonData> mOnActionButtonChangeCallback =
             this::onActionButtonChange;
-    private @Nullable TransitiveObservableSupplier<Pane, FullButtonData> mActionButtonDataSupplier;
+    private @Nullable ObservableSupplier<FullButtonData> mActionButtonDataSupplier;
 
     /** Creates the mediator. */
     public HubActionButtonMediator(PropertyModel propertyModel, PaneManager paneManager) {
@@ -31,8 +30,7 @@
         ObservableSupplier<Pane> focusedPaneSupplier = paneManager.getFocusedPaneSupplier();
 
         mActionButtonDataSupplier =
-                new TransitiveObservableSupplier<>(
-                        focusedPaneSupplier, p -> p.getActionButtonDataSupplier());
+                focusedPaneSupplier.createTransitive(Pane::getActionButtonDataSupplier);
         mActionButtonDataSupplier.addObserver(mOnActionButtonChangeCallback);
     }
 
diff --git a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubCoordinator.java b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubCoordinator.java
index 9bffe22a..683a2cf3 100644
--- a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubCoordinator.java
+++ b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubCoordinator.java
@@ -20,7 +20,6 @@
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplier;
-import org.chromium.base.supplier.TransitiveObservableSupplier;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
@@ -61,7 +60,7 @@
      * Warning: {@link #getFocusedPane()} may return null if no pane is focused or {@link
      * Pane#getHandleBackPressChangedSupplier()} contains null.
      */
-    private final TransitiveObservableSupplier<Pane, Boolean> mFocusedPaneHandleBackPressSupplier;
+    private final ObservableSupplier<Boolean> mFocusedPaneHandleBackPressSupplier;
 
     private final PaneBackStackHandler mPaneBackStackHandler;
     private final ObservableSupplier<@Nullable Tab> mCurrentTabSupplier;
@@ -101,9 +100,9 @@
         mBackPressStateChangeCallback = (ignored) -> updateHandleBackPressSupplier();
         mPaneManager = paneManager;
         mFocusedPaneHandleBackPressSupplier =
-                new TransitiveObservableSupplier<>(
-                        paneManager.getFocusedPaneSupplier(),
-                        p -> p.getHandleBackPressChangedSupplier());
+                paneManager
+                        .getFocusedPaneSupplier()
+                        .createTransitive(BackPressHandler::getHandleBackPressChangedSupplier);
         mFocusedPaneHandleBackPressSupplier.addObserver(
                 castCallback(mBackPressStateChangeCallback));
 
@@ -174,10 +173,11 @@
                         defaultPaneId);
 
         ObservableSupplier<@Nullable View> overlayViewSupplier =
-                new TransitiveObservableSupplier<Pane, @Nullable View>(
-                        mPaneManager.getFocusedPaneSupplier(),
-                        (Function<Pane, ObservableSupplier<@Nullable View>>)
-                                pane -> pane.getHubOverlayViewSupplier());
+                mPaneManager
+                        .getFocusedPaneSupplier()
+                        .createTransitive(
+                                (Function<Pane, ObservableSupplier<@Nullable View>>)
+                                        Pane::getHubOverlayViewSupplier);
         mOverlayViewManager =
                 new SingleChildViewManager(
                         mContainerView.findViewById(R.id.hub_overlay_container),
diff --git a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubToolbarMediator.java b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubToolbarMediator.java
index 89894182..a609f6b2 100644
--- a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubToolbarMediator.java
+++ b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubToolbarMediator.java
@@ -31,7 +31,6 @@
 import org.chromium.base.Callback;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.supplier.ObservableSupplier;
-import org.chromium.base.supplier.TransitiveObservableSupplier;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.hub.HubToolbarProperties.PaneButtonLookup;
@@ -128,7 +127,7 @@
             this::onHubSearchEnabledStateChange;
     private final Callback<Boolean> mOnSearchBoxVisibilityChange =
             this::onSearchBoxVisibilityChange;
-    private final TransitiveObservableSupplier<Pane, Boolean> mHairlineVisibilitySupplier;
+    private final ObservableSupplier<Boolean> mHairlineVisibilitySupplier;
 
     private final Callback<Boolean> mOnHairlineVisibilityChange = this::onHairlineVisibilityChange;
     private final Callback<@Nullable Tab> mOnCurrentTabChange = this::onCurrentTabChange;
@@ -173,9 +172,9 @@
             pane.getHubSearchBoxVisibilitySupplier().addObserver(mOnSearchBoxVisibilityChange);
         }
         mHairlineVisibilitySupplier =
-                new TransitiveObservableSupplier<>(
-                        paneManager.getFocusedPaneSupplier(),
-                        p -> p.getHairlineVisibilitySupplier());
+                paneManager
+                        .getFocusedPaneSupplier()
+                        .createTransitive(Pane::getHairlineVisibilitySupplier);
         mHairlineVisibilitySupplier.addObserver(mOnHairlineVisibilityChange);
         ObservableSupplier<Pane> focusedPaneSupplier = paneManager.getFocusedPaneSupplier();
         focusedPaneSupplier.addObserver(mOnFocusedPaneChange);
diff --git a/chrome/browser/intranet_redirect_detector.cc b/chrome/browser/intranet_redirect_detector.cc
index c17994b3..a727bdc 100644
--- a/chrome/browser/intranet_redirect_detector.cc
+++ b/chrome/browser/intranet_redirect_detector.cc
@@ -40,7 +40,6 @@
 #include "services/network/public/cpp/simple_url_loader.h"
 #include "services/network/public/mojom/network_service.mojom.h"
 
-// TODO(crbug.com/40966307): Write test to verify we handle the policy toggling.
 IntranetRedirectDetector::IntranetRedirectDetector()
     : redirect_origin_(g_browser_process->local_state()->GetString(
           prefs::kLastKnownIntranetRedirectOrigin)) {
diff --git a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtils.java b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtils.java
index 4a80dc65..9a21db2ca 100644
--- a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtils.java
+++ b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtils.java
@@ -16,6 +16,7 @@
 import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.SINGLE_TAB;
 import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.TAB_GROUP_PROMO;
 import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.TAB_GROUP_SYNC_PROMO;
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.TIPS_NOTIFICATIONS_PROMO;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -130,6 +131,8 @@
                 return "QuickDeletePromo";
             case HISTORY_SYNC_PROMO:
                 return "HistorySyncPromo";
+            case TIPS_NOTIFICATIONS_PROMO:
+                return "TipsNotificationsPromo";
             default:
                 assert false : "Module type not supported!";
                 return assumeNonNull(null);
@@ -156,6 +159,8 @@
                 return QUICK_DELETE_PROMO;
             case "HistorySyncPromo":
                 return HISTORY_SYNC_PROMO;
+            case "TipsNotificationsPromo":
+                return TIPS_NOTIFICATIONS_PROMO;
             default:
                 Log.i(TAG, "Module type %s not supported!", label);
                 return ModuleType.NUM_ENTRIES;
diff --git a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesUtils.java b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesUtils.java
index cd19961..741b8ce 100644
--- a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesUtils.java
+++ b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesUtils.java
@@ -14,6 +14,7 @@
 import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.SINGLE_TAB;
 import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.TAB_GROUP_PROMO;
 import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.TAB_GROUP_SYNC_PROMO;
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.TIPS_NOTIFICATIONS_PROMO;
 
 import android.content.Context;
 import android.os.SystemClock;
@@ -59,7 +60,8 @@
                             TAB_GROUP_PROMO,
                             TAB_GROUP_SYNC_PROMO,
                             QUICK_DELETE_PROMO,
-                            HISTORY_SYNC_PROMO));
+                            HISTORY_SYNC_PROMO,
+                            TIPS_NOTIFICATIONS_PROMO));
 
     static boolean belongsToEducationalTipModule(@ModuleType int moduleType) {
         return sEducationalTipCardList.contains(moduleType);
@@ -108,6 +110,7 @@
             case TAB_GROUP_SYNC_PROMO:
             case QUICK_DELETE_PROMO:
             case HISTORY_SYNC_PROMO:
+            case TIPS_NOTIFICATIONS_PROMO:
                 // All tips use the same name.
                 return context.getString(R.string.educational_tip_module_name);
             case AUXILIARY_SEARCH:
diff --git a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/ModuleDelegate.java b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/ModuleDelegate.java
index 4e4e0517b..117248d2 100644
--- a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/ModuleDelegate.java
+++ b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/ModuleDelegate.java
@@ -37,6 +37,7 @@
         ModuleType.TAB_GROUP_SYNC_PROMO,
         ModuleType.QUICK_DELETE_PROMO,
         ModuleType.HISTORY_SYNC_PROMO,
+        ModuleType.TIPS_NOTIFICATIONS_PROMO,
         ModuleType.NUM_ENTRIES
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -52,7 +53,8 @@
         int TAB_GROUP_SYNC_PROMO = 8;
         int QUICK_DELETE_PROMO = 9;
         int HISTORY_SYNC_PROMO = 10;
-        int NUM_ENTRIES = 11;
+        int TIPS_NOTIFICATIONS_PROMO = 11;
+        int NUM_ENTRIES = 12;
     }
 
     // LINT.ThenChange(//chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/ntp_cards/NtpCardsMediator.java:HomeModuleTypes)
diff --git a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java
index 69bae73..09808ac 100644
--- a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java
+++ b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java
@@ -261,7 +261,8 @@
                         ModuleType.TAB_GROUP_PROMO,
                         ModuleType.TAB_GROUP_SYNC_PROMO,
                         ModuleType.QUICK_DELETE_PROMO,
-                        ModuleType.HISTORY_SYNC_PROMO);
+                        ModuleType.HISTORY_SYNC_PROMO,
+                        ModuleType.TIPS_NOTIFICATIONS_PROMO);
         when(mHomeModulesConfigManager.getEnabledModuleSet())
                 .thenReturn(new HashSet<>(expectedModuleListBeforeHidingModule));
         mCoordinator = createCoordinator(/* skipInitProfile= */ false);
diff --git a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesMediatorUnitTest.java b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesMediatorUnitTest.java
index aa010bf..5da0839 100644
--- a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesMediatorUnitTest.java
+++ b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesMediatorUnitTest.java
@@ -587,7 +587,8 @@
                         ModuleType.TAB_GROUP_PROMO,
                         ModuleType.TAB_GROUP_SYNC_PROMO,
                         ModuleType.QUICK_DELETE_PROMO,
-                        ModuleType.HISTORY_SYNC_PROMO);
+                        ModuleType.HISTORY_SYNC_PROMO,
+                        ModuleType.TIPS_NOTIFICATIONS_PROMO);
         assertEquals(expectedModuleSet, mMediator.getFilteredEnabledModuleSet());
 
         // Verifies that the single tab module isn't shown if it isn't the home surface even with
@@ -602,7 +603,8 @@
                         ModuleType.TAB_GROUP_PROMO,
                         ModuleType.TAB_GROUP_SYNC_PROMO,
                         ModuleType.QUICK_DELETE_PROMO,
-                        ModuleType.HISTORY_SYNC_PROMO);
+                        ModuleType.HISTORY_SYNC_PROMO,
+                        ModuleType.TIPS_NOTIFICATIONS_PROMO);
         assertEquals(expectedModuleSet, mMediator.getFilteredEnabledModuleSet());
     }
 
diff --git a/chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker_unittest.cc b/chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker_unittest.cc
index 5d658a0f..da295923 100644
--- a/chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker_unittest.cc
+++ b/chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker_unittest.cc
@@ -111,12 +111,16 @@
   EXPECT_TRUE(instance_.is_visible());
   EXPECT_FALSE(instance_.is_audio_playing());
   histogram_tester_.ExpectTotalCount("Session.TotalDuration", 0);
+  histogram_tester_.ExpectTotalCount(
+      "PUMA.RegionalCapabilities.Session.TotalDuration.Recorded", 0);
 
   instance_.OnUserEvent();
   EXPECT_TRUE(instance_.in_session());
   EXPECT_TRUE(instance_.is_visible());
   EXPECT_FALSE(instance_.is_audio_playing());
   histogram_tester_.ExpectTotalCount("Session.TotalDuration", 0);
+  histogram_tester_.ExpectTotalCount(
+      "PUMA.RegionalCapabilities.Session.TotalDuration.Recorded", 0);
 
   // Even if there is a recent user event visibility change should end the
   // session.
@@ -127,6 +131,8 @@
   EXPECT_FALSE(instance_.is_visible());
   EXPECT_FALSE(instance_.is_audio_playing());
   histogram_tester_.ExpectTotalCount("Session.TotalDuration", 1);
+  histogram_tester_.ExpectTotalCount(
+      "PUMA.RegionalCapabilities.Session.TotalDuration.Recorded", 1);
 
   // For the second time only visibility change should start the session.
   instance_.OnVisibilityChanged(true, kZeroTime);
@@ -134,11 +140,15 @@
   EXPECT_TRUE(instance_.is_visible());
   EXPECT_FALSE(instance_.is_audio_playing());
   histogram_tester_.ExpectTotalCount("Session.TotalDuration", 1);
+  histogram_tester_.ExpectTotalCount(
+      "PUMA.RegionalCapabilities.Session.TotalDuration.Recorded", 1);
   instance_.OnVisibilityChanged(false, kZeroTime);
   EXPECT_FALSE(instance_.in_session());
   EXPECT_FALSE(instance_.is_visible());
   EXPECT_FALSE(instance_.is_audio_playing());
   histogram_tester_.ExpectTotalCount("Session.TotalDuration", 2);
+  histogram_tester_.ExpectTotalCount(
+      "PUMA.RegionalCapabilities.Session.TotalDuration.Recorded", 2);
 }
 
 TEST_F(DesktopSessionDurationTrackerTest, TestUserEvent) {
diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc
index 3c105597..63c1941 100644
--- a/chrome/browser/net/system_network_context_manager.cc
+++ b/chrome/browser/net/system_network_context_manager.cc
@@ -47,7 +47,6 @@
 #include "components/embedder_support/user_agent_utils.h"
 #include "components/net_log/net_export_file_writer.h"
 #include "components/net_log/net_log_proxy_source.h"
-#include "components/network_session_configurator/common/network_features.h"
 #include "components/os_crypt/sync/os_crypt.h"
 #include "components/policy/core/common/policy_namespace.h"
 #include "components/policy/core/common/policy_service.h"
diff --git a/chrome/browser/net/websocket_browsertest.cc b/chrome/browser/net/websocket_browsertest.cc
index 42e662d..c71950d 100644
--- a/chrome/browser/net/websocket_browsertest.cc
+++ b/chrome/browser/net/websocket_browsertest.cc
@@ -293,8 +293,6 @@
   net::EmbeddedTestServer https_server_;
 };
 
-// TODO(crbug.com/434744665): add tests for websockets opened from shared
-// workers.
 class LocalNetworkAccessWebSocketsBrowserTest
     : public WebSocketBrowserHTTPSConnectToTest {
  public:
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMetricsUtils.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMetricsUtils.java
index 7e7be13..88bcd75f 100644
--- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMetricsUtils.java
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMetricsUtils.java
@@ -10,6 +10,7 @@
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.chrome.browser.magic_stack.ModuleDelegate;
 import org.chromium.chrome.browser.ntp_customization.NtpCustomizationUtils.NtpBackgroundImageType;
+import org.chromium.chrome.browser.ntp_customization.theme.chrome_colors.NtpThemeColorInfo.NtpThemeColorId;
 import org.chromium.chrome.browser.ntp_customization.theme.upload_image.UploadImagePreviewCoordinator;
 
 /** The utility class for logging the NTP customization bottom sheet's metrics. */
@@ -62,6 +63,12 @@
     static final String HISTOGRAM_THEME_COLLECTION_SELECTED =
             HISTOGRAM_THEME_COLLECTION + ".CollectionSelected";
 
+    @VisibleForTesting
+    static final String HISTOGRAM_THEME_CHROME_COLOR = "NewTabPage.Customization.Theme.ChromeColor";
+
+    @VisibleForTesting
+    static final String HISTOGRAM_THEME_CHROME_COLOR_ID = HISTOGRAM_THEME_CHROME_COLOR + ".Click";
+
     /**
      * Records the type of theme selected for the New Tab Page background. This is logged once on
      * cold startup.
@@ -172,4 +179,14 @@
         RecordHistogram.recordSparseHistogram(
                 HISTOGRAM_THEME_COLLECTION_SELECTED, themeCollectionHash);
     }
+
+    /**
+     * Records when a Chrome color is selected from the Chrome Colors bottom sheet.
+     *
+     * @param themeColorId The ID of the color.
+     */
+    public static void recordChromeColorId(@NtpThemeColorId int themeColorId) {
+        RecordHistogram.recordEnumeratedHistogram(
+                HISTOGRAM_THEME_CHROME_COLOR_ID, themeColorId, NtpThemeColorId.NUM_ENTRIES);
+    }
 }
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/theme/chrome_colors/NtpChromeColorsCoordinator.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/theme/chrome_colors/NtpChromeColorsCoordinator.java
index 6fa9e77..f1cf93b 100644
--- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/theme/chrome_colors/NtpChromeColorsCoordinator.java
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/theme/chrome_colors/NtpChromeColorsCoordinator.java
@@ -32,6 +32,7 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.ntp_customization.BottomSheetDelegate;
 import org.chromium.chrome.browser.ntp_customization.NtpCustomizationConfigManager;
+import org.chromium.chrome.browser.ntp_customization.NtpCustomizationMetricsUtils;
 import org.chromium.chrome.browser.ntp_customization.NtpCustomizationUtils;
 import org.chromium.chrome.browser.ntp_customization.NtpCustomizationUtils.NtpBackgroundImageType;
 import org.chromium.chrome.browser.ntp_customization.R;
@@ -58,6 +59,7 @@
     private final int mSpacing;
     private final Runnable mOnChromeColorSelectedCallback;
     private final @Nullable NtpThemeColorInfo mPrimaryColorInfo;
+    private @Nullable NtpThemeColorInfo mLastClickedColorInfo;
     private @Nullable EditText mBackgroundColorInput;
     private @Nullable EditText mPrimaryColorInput;
     private @Nullable ImageView mBackgroundColorCircleImageView;
@@ -166,10 +168,15 @@
         NtpCustomizationConfigManager.getInstance()
                 .onBackgroundColorChanged(mContext, ntpThemeColorInfo, newType);
         mOnChromeColorSelectedCallback.run();
+        mLastClickedColorInfo = ntpThemeColorInfo;
     }
 
     /** Cleans up the resources used by this coordinator. */
     public void destroy() {
+        if (mLastClickedColorInfo != null) {
+            NtpCustomizationMetricsUtils.recordChromeColorId(mLastClickedColorInfo.id);
+        }
+
         mBackButton.setOnClickListener(null);
         mLearnMoreButton.setOnClickListener(null);
         mChromeColorsList.clear();
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/theme/chrome_colors/NtpThemeColorInfo.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/theme/chrome_colors/NtpThemeColorInfo.java
index 6554807..19b9e95 100644
--- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/theme/chrome_colors/NtpThemeColorInfo.java
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/theme/chrome_colors/NtpThemeColorInfo.java
@@ -23,6 +23,7 @@
 /** The data class for NTP's theme color. */
 @NullMarked
 public class NtpThemeColorInfo {
+    // LINT.IfChange(NtpThemeColorId)
     @IntDef({
         NtpThemeColorId.DEFAULT,
         NtpThemeColorId.NTP_COLORS_BLUE,
@@ -51,6 +52,8 @@
         int NUM_ENTRIES = 10;
     }
 
+    // LINT.ThenChange(//tools/metrics/histograms/metadata/new_tab_page/enums.xml:NtpThemeColorId)
+
     public static final int COLOR_NOT_SET = -1;
 
     public @NtpThemeColorId int id;
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMetricsUtilsUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMetricsUtilsUnitTest.java
index 9f92504..016828575 100644
--- a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMetricsUtilsUnitTest.java
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMetricsUtilsUnitTest.java
@@ -14,6 +14,15 @@
 import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationUtils.NtpBackgroundImageType.DEFAULT;
 import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationUtils.NtpBackgroundImageType.IMAGE_FROM_DISK;
 import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationUtils.NtpBackgroundImageType.THEME_COLLECTION;
+import static org.chromium.chrome.browser.ntp_customization.theme.chrome_colors.NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_AQUA;
+import static org.chromium.chrome.browser.ntp_customization.theme.chrome_colors.NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_BLUE;
+import static org.chromium.chrome.browser.ntp_customization.theme.chrome_colors.NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_CITRON;
+import static org.chromium.chrome.browser.ntp_customization.theme.chrome_colors.NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_FUCHSIA;
+import static org.chromium.chrome.browser.ntp_customization.theme.chrome_colors.NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_GREEN;
+import static org.chromium.chrome.browser.ntp_customization.theme.chrome_colors.NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_ORANGE;
+import static org.chromium.chrome.browser.ntp_customization.theme.chrome_colors.NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_ROSE;
+import static org.chromium.chrome.browser.ntp_customization.theme.chrome_colors.NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_VIOLET;
+import static org.chromium.chrome.browser.ntp_customization.theme.chrome_colors.NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_VIRIDIAN;
 import static org.chromium.chrome.browser.ntp_customization.theme.upload_image.UploadImagePreviewCoordinator.PreviewInteractionType.CANCEL;
 import static org.chromium.chrome.browser.ntp_customization.theme.upload_image.UploadImagePreviewCoordinator.PreviewInteractionType.SAVE;
 
@@ -31,6 +40,7 @@
 import org.chromium.chrome.browser.magic_stack.ModuleDelegate;
 import org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType;
 import org.chromium.chrome.browser.ntp_customization.NtpCustomizationUtils.NtpBackgroundImageType;
+import org.chromium.chrome.browser.ntp_customization.theme.chrome_colors.NtpThemeColorInfo.NtpThemeColorId;
 import org.chromium.chrome.browser.ntp_customization.theme.upload_image.UploadImagePreviewCoordinator.PreviewInteractionType;
 
 /** Unit tests for {@link NtpCustomizationMetricsUtils} */
@@ -178,4 +188,29 @@
         NtpCustomizationMetricsUtils.recordThemeCollectionSelected(themeCollectionHash);
         histogramWatcher.assertExpected();
     }
+
+    @Test
+    public void testRecordChromeColorSelected() {
+        String histogramName = "NewTabPage.Customization.Theme.ChromeColor.Click";
+        @NtpThemeColorId
+        int[] chromeColorIds =
+                new int[] {
+                    NTP_COLORS_BLUE,
+                    NTP_COLORS_AQUA,
+                    NTP_COLORS_GREEN,
+                    NTP_COLORS_VIRIDIAN,
+                    NTP_COLORS_CITRON,
+                    NTP_COLORS_ORANGE,
+                    NTP_COLORS_ROSE,
+                    NTP_COLORS_FUCHSIA,
+                    NTP_COLORS_VIOLET
+                };
+
+        for (@NtpThemeColorId int colorId : chromeColorIds) {
+            HistogramWatcher histogramWatcher =
+                    HistogramWatcher.newSingleRecordWatcher(histogramName, colorId);
+            NtpCustomizationMetricsUtils.recordChromeColorId(colorId);
+            histogramWatcher.assertExpected();
+        }
+    }
 }
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/theme/chrome_colors/NtpChromeColorsCoordinatorUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/theme/chrome_colors/NtpChromeColorsCoordinatorUnitTest.java
index 28fb738..22e72b8 100644
--- a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/theme/chrome_colors/NtpChromeColorsCoordinatorUnitTest.java
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/theme/chrome_colors/NtpChromeColorsCoordinatorUnitTest.java
@@ -44,6 +44,7 @@
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Features;
+import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.ntp_customization.BottomSheetDelegate;
@@ -118,6 +119,52 @@
     }
 
     @Test
+    public void testDestroy_logMetricsWithSingleClick() {
+        String histogramName = "NewTabPage.Customization.Theme.ChromeColor.Click";
+        createCoordinator();
+
+        NtpThemeColorInfo blueColor =
+                NtpThemeColorUtils.createNtpThemeColorInfo(
+                        mContext, NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_BLUE);
+        mCoordinator.onItemClicked(blueColor);
+
+        HistogramWatcher watcher =
+                HistogramWatcher.newSingleRecordWatcher(histogramName, blueColor.id);
+        mCoordinator.destroy();
+        watcher.assertExpected();
+
+        NtpCustomizationUtils.resetSharedPreferenceForTesting();
+    }
+
+    @Test
+    public void testDestroy_logMetricsWithMultipleClicks() {
+        String histogramName = "NewTabPage.Customization.Theme.ChromeColor.Click";
+        createCoordinator();
+
+        NtpThemeColorInfo blueColor =
+                NtpThemeColorUtils.createNtpThemeColorInfo(
+                        mContext, NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_BLUE);
+        mCoordinator.onItemClicked(blueColor);
+
+        NtpThemeColorInfo aquaColor =
+                NtpThemeColorUtils.createNtpThemeColorInfo(
+                        mContext, NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_AQUA);
+        mCoordinator.onItemClicked(aquaColor);
+
+        NtpThemeColorInfo greenColor =
+                NtpThemeColorUtils.createNtpThemeColorInfo(
+                        mContext, NtpThemeColorInfo.NtpThemeColorId.NTP_COLORS_GREEN);
+        mCoordinator.onItemClicked(greenColor);
+
+        HistogramWatcher watcher =
+                HistogramWatcher.newSingleRecordWatcher(histogramName, greenColor.id);
+        mCoordinator.destroy();
+        watcher.assertExpected();
+
+        NtpCustomizationUtils.resetSharedPreferenceForTesting();
+    }
+
+    @Test
     public void testColorGridView_onMeasure() {
         ColorGridView gridView = new ColorGridView(mContext, null);
         GridLayoutManager layoutManager = spy(new GridLayoutManager(mContext, 1));
diff --git a/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc b/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc
index d944588..06f789c1 100644
--- a/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc
+++ b/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc
@@ -230,7 +230,6 @@
 }
 
 // TODO(crbug.com/40936591): This test is flaky on ChromeOS and Linux.
-// TODO(crbug.com/382573509): and flaky on Windows.
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
 #define MAYBE_LargestContentfulPaint_SubframeInput \
   DISABLED_LargestContentfulPaint_SubframeInput
diff --git a/chrome/browser/password_manager/password_change/change_password_form_waiter.cc b/chrome/browser/password_manager/password_change/change_password_form_waiter.cc
index 4b18be1..f2562ab 100644
--- a/chrome/browser/password_manager/password_change/change_password_form_waiter.cc
+++ b/chrome/browser/password_manager/password_change/change_password_form_waiter.cc
@@ -253,7 +253,7 @@
 }
 
 void ChangePasswordFormWaiter::DidStopLoading() {
-  if (web_contents()->IsLoading()) {
+  if (web_contents()->IsLoading() || model_loaded_subscription_) {
     return;
   }
   timeout_timer_.Start(FROM_HERE, timeout_, this,
diff --git a/chrome/browser/password_manager/password_change/change_password_form_waiter_unittest.cc b/chrome/browser/password_manager/password_change/change_password_form_waiter_unittest.cc
index 07529a1..f757116 100644
--- a/chrome/browser/password_manager/password_change/change_password_form_waiter_unittest.cc
+++ b/chrome/browser/password_manager/password_change/change_password_form_waiter_unittest.cc
@@ -66,23 +66,31 @@
               (override));
 };
 
-class MockFieldClassificationModelHandler
+class FakeFieldClassificationModelHandler
     : public autofill::FieldClassificationModelHandler {
  public:
-  MockFieldClassificationModelHandler(
+  FakeFieldClassificationModelHandler(
       optimization_guide::TestOptimizationGuideModelProvider* model_provider)
       : FieldClassificationModelHandler(
             model_provider,
             optimization_guide::proto::OptimizationTarget::
-                OPTIMIZATION_TARGET_AUTOFILL_FIELD_CLASSIFICATION) {}
+                OPTIMIZATION_TARGET_PASSWORD_MANAGER_FORM_CLASSIFICATION) {}
+  ~FakeFieldClassificationModelHandler() override = default;
 
-  MOCK_METHOD(base::CallbackListSubscription,
-              RegisterModelChangeCallback,
-              (autofill::FieldClassificationModelHandler::
-                   ModelChangeCallbackList::CallbackType),
-              (override));
+  void NotifyAboutModelChange() { model_change_callback_list_.Notify(); }
 
-  MOCK_METHOD(bool, ModelAvailable, (), (override, const));
+  void SetModelAvailability(bool available) { is_model_available_ = available; }
+
+  // autofill::FieldClassificationModelHandler
+  bool ModelAvailable() const override { return is_model_available_; }
+  base::CallbackListSubscription RegisterModelChangeCallback(
+      ModelChangeCallbackList::CallbackType callback) override {
+    return model_change_callback_list_.Add(std::move(callback));
+  }
+
+ private:
+  bool is_model_available_ = false;
+  ModelChangeCallbackList model_change_callback_list_;
 };
 
 autofill::FormFieldData CreateNonFocusableTestFormField(
@@ -118,7 +126,7 @@
         optimization_guide::TestOptimizationGuideModelProvider>();
 
     autofill_client()->set_password_ml_prediction_model_handler(
-        std::make_unique<MockFieldClassificationModelHandler>(
+        std::make_unique<FakeFieldClassificationModelHandler>(
             model_provider_.get()));
 
     ON_CALL(client_, GetProfilePasswordStore)
@@ -155,8 +163,8 @@
     return autofill_client_injector_[web_contents()];
   }
 
-  MockFieldClassificationModelHandler* model_handler() {
-    return static_cast<MockFieldClassificationModelHandler*>(
+  FakeFieldClassificationModelHandler* model_handler() {
+    return static_cast<FakeFieldClassificationModelHandler*>(
         autofill_client()->GetPasswordManagerFieldClassificationModelHandler());
   }
 
@@ -704,18 +712,11 @@
       {password_manager::features::kPasswordFormClientsideClassifier},
       {password_manager::features::kDownloadModelForPasswordChange});
 
-  // Since the feature is disabled, the model handler should not be called.
-  // EXPECT_CALL(*model_handler(), ModelAvailable).Times(0);
-
   base::MockOnceCallback<void(password_manager::PasswordFormManager*)>
       completion_callback;
   auto waiter = ChangePasswordFormWaiter::Builder(web_contents(), client(),
                                                   completion_callback.Get())
                     .Build();
-  ON_CALL(*model_handler(), ModelAvailable).WillByDefault(Return(true));
-  ON_CALL(*model_handler(), RegisterModelChangeCallback).WillByDefault([] {
-    return base::CallbackListSubscription();
-  });
 
   // The rest of the test is similar to PasswordChangeFormIdentified.
   std::vector<autofill::FormFieldData> fields;
@@ -754,17 +755,13 @@
        password_manager::features::kDownloadModelForPasswordChange},
       {});
 
-  EXPECT_CALL(*model_handler(), ModelAvailable).WillOnce(Return(true));
+  model_handler()->SetModelAvailability(/*available=*/true);
 
   base::MockOnceCallback<void(password_manager::PasswordFormManager*)>
       completion_callback;
   auto waiter = ChangePasswordFormWaiter::Builder(web_contents(), client(),
                                                   completion_callback.Get())
                     .Build();
-  ON_CALL(*model_handler(), ModelAvailable).WillByDefault(Return(true));
-  ON_CALL(*model_handler(), RegisterModelChangeCallback).WillByDefault([] {
-    return base::CallbackListSubscription();
-  });
 
   // The rest of the test is similar to PasswordChangeFormIdentified.
   std::vector<autofill::FormFieldData> fields;
@@ -802,15 +799,7 @@
       {password_manager::features::kPasswordFormClientsideClassifier,
        password_manager::features::kDownloadModelForPasswordChange},
       {});
-
-  EXPECT_CALL(*model_handler(), ModelAvailable).WillOnce(Return(false));
-
-  base::RepeatingClosure model_changed_callback;
-  EXPECT_CALL(*model_handler(), RegisterModelChangeCallback)
-      .WillOnce([&](base::RepeatingClosure callback) {
-        model_changed_callback = callback;
-        return base::CallbackListSubscription();
-      });
+  model_handler()->SetModelAvailability(/*available=*/false);
 
   std::vector<autofill::FormFieldData> fields;
   fields.push_back(CreateTestFormField(
@@ -836,11 +825,6 @@
                                                   result_future.GetCallback())
                     .Build();
 
-  ON_CALL(*model_handler(), ModelAvailable).WillByDefault(Return(false));
-  ON_CALL(*model_handler(), RegisterModelChangeCallback).WillByDefault([] {
-    return base::CallbackListSubscription();
-  });
-
   // Model is not available yet, so the callback should not be called.
   EXPECT_FALSE(result_future.IsReady());
 
@@ -850,7 +834,7 @@
         .WillOnce(base::test::RunOnceCallback<1>(true));
   }
   // Simulate the model becoming available.
-  model_changed_callback.Run();
+  model_handler()->NotifyAboutModelChange();
 
   EXPECT_EQ(result_future.Get(), form_managers.back().get());
 }
@@ -862,14 +846,7 @@
        password_manager::features::kDownloadModelForPasswordChange},
       {});
 
-  EXPECT_CALL(*model_handler(), ModelAvailable).WillOnce(Return(false));
-
-  base::RepeatingClosure model_changed_callback;
-  EXPECT_CALL(*model_handler(), RegisterModelChangeCallback)
-      .WillOnce([&](base::RepeatingClosure callback) {
-        model_changed_callback = callback;
-        return base::CallbackListSubscription();
-      });
+  model_handler()->SetModelAvailability(/*available=*/false);
 
   base::MockOnceCallback<void(password_manager::PasswordFormManager*)>
       completion_callback;
@@ -878,21 +855,18 @@
                                                   completion_callback.Get())
                     .SetTimeoutCallback(timeout_callback.Get())
                     .Build();
-  ON_CALL(*model_handler(), ModelAvailable).WillByDefault(Return(false));
-  ON_CALL(*model_handler(), RegisterModelChangeCallback).WillByDefault([] {
-    return base::CallbackListSubscription();
-  });
 
   // Model is not available yet, so the callback should not be called.
   EXPECT_CALL(completion_callback, Run).Times(0);
+  EXPECT_CALL(timeout_callback, Run).Times(0);
 
   // Timeout should not be triggered even if the model is not available.
+  static_cast<content::WebContentsObserver*>(waiter.get())->DidStopLoading();
   task_environment()->FastForwardBy(
       ChangePasswordFormWaiter::kChangePasswordFormWaitingTimeout * 2);
 
   // Simulate the model becoming available.
-  ASSERT_TRUE(model_changed_callback);
-  model_changed_callback.Run();
+  model_handler()->NotifyAboutModelChange();
 
   static_cast<content::WebContentsObserver*>(waiter.get())->DidStopLoading();
   EXPECT_CALL(timeout_callback, Run());
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index 58701d164..6f88a5ec 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -1633,9 +1633,6 @@
   EXPECT_EQ(base::UTF16ToUTF8(view->GetSelectedText()), kExpectedText);
 }
 
-// TODO(crbug.com/40793934): Add tests for using space and shift+space shortcuts
-// for scrolling PDFs.
-
 // Test that even if a different tab is selected when a navigation occurs,
 // the correct tab still gets navigated (see crbug.com/672563).
 IN_PROC_BROWSER_TEST_P(PDFExtensionTest, NavigationOnCorrectTab) {
diff --git a/chrome/browser/picture_in_picture/auto_picture_in_picture_safe_browsing_checker_client_unittest.cc b/chrome/browser/picture_in_picture/auto_picture_in_picture_safe_browsing_checker_client_unittest.cc
index be7dfabd..7c09cb2 100644
--- a/chrome/browser/picture_in_picture/auto_picture_in_picture_safe_browsing_checker_client_unittest.cc
+++ b/chrome/browser/picture_in_picture/auto_picture_in_picture_safe_browsing_checker_client_unittest.cc
@@ -60,9 +60,10 @@
 #if DCHECK_IS_ON()
 TEST_F(AutoPictureInPictureSafeBrowsingCheckerClientTest, InvalidDelay) {
   EXPECT_DEATH_IF_SUPPORTED(
-      std::make_unique<AutoPictureInPictureSafeBrowsingCheckerClient>(
-          mock_database_manager(), base::Milliseconds(1),
-          report_url_safety_cb().Get()),
+      std::ignore =
+          std::make_unique<AutoPictureInPictureSafeBrowsingCheckerClient>(
+              mock_database_manager(), base::Milliseconds(1),
+              report_url_safety_cb().Get()),
       "");
 }
 #endif  // DCHECK_IS_ON()
diff --git a/chrome/browser/policy/networking/user_network_configuration_updater_factory.cc b/chrome/browser/policy/networking/user_network_configuration_updater_factory.cc
index b527cd8..9f14a68 100644
--- a/chrome/browser/policy/networking/user_network_configuration_updater_factory.cc
+++ b/chrome/browser/policy/networking/user_network_configuration_updater_factory.cc
@@ -77,8 +77,6 @@
   // expect to have UserNetworkConfigurationUpdater, because
   // ManagedNetworkConfigurationHandler requires a (possibly empty) policy to be
   // set for all user sessions.
-  // TODO(crbug.com/40097732): Evaluate if this is can be solved in a
-  // more elegant way.
   return UserNetworkConfigurationUpdaterAsh::CreateForUserPolicy(
       profile, *user, profile->GetProfilePolicyConnector()->policy_service(),
       ash::NetworkHandler::Get()->managed_network_configuration_handler());
diff --git a/chrome/browser/renderer_host/javascript_optimizer_feature_browsertest.cc b/chrome/browser/renderer_host/javascript_optimizer_feature_browsertest.cc
index d83f650..da2fe69 100644
--- a/chrome/browser/renderer_host/javascript_optimizer_feature_browsertest.cc
+++ b/chrome/browser/renderer_host/javascript_optimizer_feature_browsertest.cc
@@ -18,6 +18,8 @@
 #include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
 #include "chrome/browser/ui/views/location_bar/icon_label_bubble_view.h"
 #include "chrome/browser/ui/views/page_action/action_ids.h"
+#include "chrome/browser/ui/views/page_action/test_support/page_action_interactive_test_mixin.h"
+#include "chrome/test/interaction/interactive_browser_test.h"
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 #include "chrome/common/chrome_features.h"
@@ -1117,15 +1119,42 @@
 
 #if !BUILDFLAG(IS_ANDROID)
 class JavascriptOptimizerOmnibarIconBrowserTest
-    : public JavascriptOptimizerBrowserTest {
+    : public PageActionInteractiveTestMixin<InteractiveBrowserTest> {
  public:
+  void SetUpOnMainThread() override {
+    InteractiveBrowserTest::SetUpOnMainThread();
+    host_resolver()->AddRule("*", "127.0.0.1");
+    content::SetupCrossSiteRedirector(&embedded_https_test_server());
+
+    embedded_https_test_server().SetCertHostnames(
+        {"a.com", "*.a.com", "b.com", "*.b.com", "unrelated.com"});
+    ASSERT_TRUE(embedded_https_test_server().Start());
+  }
+
+  content::WebContents* web_contents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
+  Profile* profile() { return browser()->profile(); }
+
+  bool AreV8OptimizationsDisabledForRenderFrame(content::RenderFrameHost* rfh) {
+    return rfh->GetProcess()->AreV8OptimizationsDisabled();
+  }
+
+  bool AreV8OptimizationsDisabledOnActiveWebContents() {
+    return AreV8OptimizationsDisabledForRenderFrame(
+        web_contents()->GetPrimaryMainFrame());
+  }
+
   // Returns true iff the JS Optimizations omnibar icon is visible.
-  bool GetOmnibarIconVisible() {
+  bool IsOmnibarIconVisible() {
     const auto* view = BrowserView::GetBrowserViewForBrowser(browser())
                            ->toolbar_button_provider()
                            ->GetPageActionView(kActionShowJsOptimizationsIcon);
     return view && view->GetVisible();
   }
+
+  using PageActionInteractiveTestMixin::WaitForPageActionButtonVisible;
 };
 
 class JavascriptOptimizerOmnibarIconBrowserTest_WithFlag
@@ -1141,12 +1170,8 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-class JavascriptOptimizerOmnibarIconBrowserTest_WithoutFlag
-    : public JavascriptOptimizerOmnibarIconBrowserTest {};
-
-// TODO(crbug.com/462704642): Re-enable this test
 IN_PROC_BROWSER_TEST_F(JavascriptOptimizerOmnibarIconBrowserTest_WithFlag,
-                       DISABLED_IconShowsWhenOptimizationsDisabled) {
+                       IconShowsWhenOptimizationsDisabled) {
   auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
   map->SetDefaultContentSetting(ContentSettingsType::JAVASCRIPT_OPTIMIZER,
                                 ContentSetting::CONTENT_SETTING_BLOCK);
@@ -1154,7 +1179,9 @@
   ASSERT_TRUE(content::NavigateToURL(
       web_contents(), embedded_https_test_server().GetURL("/simple.html")));
   ASSERT_TRUE(AreV8OptimizationsDisabledOnActiveWebContents());
-  EXPECT_TRUE(GetOmnibarIconVisible());
+  RunTestSequence(
+      WaitForPageActionButtonVisible(kActionShowJsOptimizationsIcon));
+  EXPECT_TRUE(IsOmnibarIconVisible());
 }
 
 IN_PROC_BROWSER_TEST_F(JavascriptOptimizerOmnibarIconBrowserTest_WithFlag,
@@ -1166,13 +1193,14 @@
   ASSERT_TRUE(content::NavigateToURL(
       web_contents(), embedded_https_test_server().GetURL("/simple.html")));
   ASSERT_FALSE(AreV8OptimizationsDisabledOnActiveWebContents());
-  EXPECT_FALSE(GetOmnibarIconVisible());
+  RunTestSequence(
+      WaitForPageActionChipNotVisible(kActionShowJsOptimizationsIcon));
+  EXPECT_FALSE(IsOmnibarIconVisible());
 }
 
-// TODO(crbug.com/462704642): Re-enable this test
 IN_PROC_BROWSER_TEST_F(
     JavascriptOptimizerOmnibarIconBrowserTest_WithFlag,
-    DISABLED_IconShowsWhenNavigatingToPageWhereOptimizationsDisabled) {
+    IconShowsWhenNavigatingToPageWhereOptimizationsDisabled) {
   auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
   // Optimizations enabled for all except a.com
   map->SetDefaultContentSetting(ContentSettingsType::JAVASCRIPT_OPTIMIZER,
@@ -1188,19 +1216,22 @@
       web_contents(),
       embedded_https_test_server().GetURL("b.com", "/simple.html")));
   ASSERT_FALSE(AreV8OptimizationsDisabledOnActiveWebContents());
-  EXPECT_FALSE(GetOmnibarIconVisible());
+  RunTestSequence(
+      WaitForPageActionChipNotVisible(kActionShowJsOptimizationsIcon));
+  EXPECT_FALSE(IsOmnibarIconVisible());
   // After navigating to a.com, icon is visible.
   ASSERT_TRUE(content::NavigateToURL(
       web_contents(),
       embedded_https_test_server().GetURL("a.com", "/simple.html")));
   ASSERT_TRUE(AreV8OptimizationsDisabledOnActiveWebContents());
-  EXPECT_TRUE(GetOmnibarIconVisible());
+  RunTestSequence(
+      WaitForPageActionButtonVisible(kActionShowJsOptimizationsIcon));
+  EXPECT_TRUE(IsOmnibarIconVisible());
 }
 
-// TODO(crbug.com/462704642): Re-enable this test
 IN_PROC_BROWSER_TEST_F(
     JavascriptOptimizerOmnibarIconBrowserTest_WithFlag,
-    DISABLED_IconDisappearsWhenNavigatingToPageWhereOptimizationsNotDisabled) {
+    IconDisappearsWhenNavigatingToPageWhereOptimizationsNotDisabled) {
   auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
   // Optimizations enabled for all except a.com
   map->SetDefaultContentSetting(ContentSettingsType::JAVASCRIPT_OPTIMIZER,
@@ -1216,15 +1247,22 @@
       web_contents(),
       embedded_https_test_server().GetURL("a.com", "/simple.html")));
   ASSERT_TRUE(AreV8OptimizationsDisabledOnActiveWebContents());
-  EXPECT_TRUE(GetOmnibarIconVisible());
+  RunTestSequence(
+      WaitForPageActionButtonVisible(kActionShowJsOptimizationsIcon));
+  EXPECT_TRUE(IsOmnibarIconVisible());
   // After navigating to b.com, icon is not visible.
   ASSERT_TRUE(content::NavigateToURL(
       web_contents(),
       embedded_https_test_server().GetURL("b.com", "/simple.html")));
   ASSERT_FALSE(AreV8OptimizationsDisabledOnActiveWebContents());
-  EXPECT_FALSE(GetOmnibarIconVisible());
+  RunTestSequence(
+      WaitForPageActionChipNotVisible(kActionShowJsOptimizationsIcon));
+  EXPECT_FALSE(IsOmnibarIconVisible());
 }
 
+class JavascriptOptimizerOmnibarIconBrowserTest_WithoutFlag
+    : public JavascriptOptimizerOmnibarIconBrowserTest {};
+
 IN_PROC_BROWSER_TEST_F(JavascriptOptimizerOmnibarIconBrowserTest_WithoutFlag,
                        IconDoesNotShowWhenFlagNotEnabled) {
   auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
@@ -1235,6 +1273,11 @@
       web_contents(), embedded_https_test_server().GetURL("/simple.html")));
   // V8 optimizations are disabled, but omnibar icon is not visible.
   ASSERT_TRUE(AreV8OptimizationsDisabledOnActiveWebContents());
-  EXPECT_FALSE(GetOmnibarIconVisible());
+  const auto* icon_view =
+      BrowserView::GetBrowserViewForBrowser(browser())
+          ->toolbar_button_provider()
+          ->GetPageActionView(kActionShowJsOptimizationsIcon);
+  // There is no view initialized because the flag is disabled.
+  ASSERT_EQ(icon_view, nullptr);
 }
 #endif  // !BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/resource_coordinator/tab_manager.h b/chrome/browser/resource_coordinator/tab_manager.h
index bae3a58..c51e3e0 100644
--- a/chrome/browser/resource_coordinator/tab_manager.h
+++ b/chrome/browser/resource_coordinator/tab_manager.h
@@ -101,6 +101,8 @@
   FRIEND_TEST_ALL_PREFIXES(TabManagerTest,
                            UrgentFastShutdownWithBeforeunloadHandler);
   FRIEND_TEST_ALL_PREFIXES(TabManagerTest, UrgentFastShutdownWithUnloadHandler);
+  FRIEND_TEST_ALL_PREFIXES(TabManagerIgnoreWorkersTest,
+                           UrgentFastShutdownWithWorker);
 
   // Returns true if the |url| represents an internal Chrome web UI page that
   // can be easily reloaded and hence makes a good choice to discard.
diff --git a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
index 7a89dab1..bdaeb95a 100644
--- a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
@@ -13,6 +13,7 @@
 #include "base/memory/memory_pressure_listener.h"
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/run_until.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
@@ -711,6 +712,60 @@
       tab_manager()->DiscardTabImpl(LifecycleUnitDiscardReason::URGENT));
 }
 
+class TabManagerIgnoreWorkersTest : public TabManagerTest {
+ public:
+  TabManagerIgnoreWorkersTest() {
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{features::kWebContentsDiscard,
+          {{"urgent_discard_ignore_workers", "true"}}}},
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(TabManagerIgnoreWorkersTest,
+                       UrgentFastShutdownWithWorker) {
+  base::HistogramTester histogram_tester;
+
+  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
+  ASSERT_TRUE(embedded_test_server()->Start());
+  OpenTwoTabs(embedded_test_server()->GetURL("a.com", "/title1.html"),
+              embedded_test_server()->GetURL("/register_service_worker.html"));
+
+  // Wait for the service worker to become ready.
+  content::RenderFrameHost* main_frame =
+      tsm()->GetWebContentsAt(1)->GetPrimaryMainFrame();
+  EXPECT_EQ("DONE",
+            EvalJs(main_frame, "register('/fetch_event_passthrough.js')"));
+
+  // Advance time so everything is urgent discardable.
+  test_tick_clock_.Advance(kBackgroundUrgentProtectionTime);
+
+  // The Tab Manager will not be able to safely fast-kill either of the tabs as
+  // one of them is current, and the other has a service worker. An unsafe
+  // attempt will be made on some platforms.
+#if BUILDFLAG(IS_CHROMEOS)
+  // The unsafe attempt for ChromeOS should succeed as ChromeOS ignores unload
+  // handlers and workers when in critical condition.
+  WindowedRenderProcessHostExitObserver observer;
+#endif  // BUILDFLAG(IS_CHROMEOS)
+  EXPECT_TRUE(
+      tab_manager()->DiscardTabImpl(LifecycleUnitDiscardReason::URGENT));
+#if BUILDFLAG(IS_CHROMEOS)
+  observer.Wait();
+  histogram_tester.ExpectBucketCount(
+      "Discarding.AttemptFastKillForDiscardResult",
+      AttemptFastKillForDiscardResult::kKilledWithoutUnloadHandlersAndWorkers,
+      1);
+#else
+  histogram_tester.ExpectBucketCount(
+      "Discarding.AttemptFastKillForDiscardResult",
+      AttemptFastKillForDiscardResult::kSkipped, 1);
+#endif  // BUILDFLAG(IS_CHROMEOS)
+}
+
 // Verifies the following state transitions for a tab:
 // - Initial state: ACTIVE
 // - Discard(kUrgent): ACTIVE->DISCARDED
@@ -1136,6 +1191,13 @@
 
 INSTANTIATE_TEST_SUITE_P(
     ,
+    TabManagerIgnoreWorkersTest,
+    ::testing::Values(true),
+    [](const ::testing::TestParamInfo<TabManagerTestWithTwoTabs::ParamType>&
+           info) { return "RetainedWebContents"; });
+
+INSTANTIATE_TEST_SUITE_P(
+    ,
     TabManagerFencedFrameTest,
     ::testing::Values(false, true),
     [](const ::testing::TestParamInfo<TabManagerFencedFrameTest::ParamType>&
diff --git a/chrome/browser/resource_coordinator/utils.cc b/chrome/browser/resource_coordinator/utils.cc
index 735cfdfc..e9b457a4 100644
--- a/chrome/browser/resource_coordinator/utils.cc
+++ b/chrome/browser/resource_coordinator/utils.cc
@@ -13,22 +13,11 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/common/content_features.h"
 #include "third_party/blink/public/mojom/frame/sudden_termination_disabler_type.mojom.h"
 
 namespace resource_coordinator {
 
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-//
-// LINT.IfChange(AttemptFastKillForDiscardResult)
-enum class AttemptFastKillForDiscardResult {
-  kKilled = 0,
-  kSkipped = 1,
-  kKilledWithoutUnloadHandlers = 2,
-  kMaxValue = kKilledWithoutUnloadHandlers,
-};
-// LINT.ThenChange(//tools/metrics/histograms/metadata/tab/enums.xml:AttemptFastKillForDiscardResult)
-
 TabLifecycleUnitSource* GetTabLifecycleUnitSource() {
   DCHECK(g_browser_process);
   auto* source = g_browser_process->resource_coordinator_parts()
@@ -56,12 +45,19 @@
       discard_reason == ::mojom::LifecycleUnitDiscardReason::URGENT) {
     // We avoid fast shutdown on tabs with beforeunload handlers on the main
     // frame, as that is often an indication of unsaved user state.
+    static const bool should_ignore_workers =
+        features::kUrgentDiscardIgnoreWorkers.Get();
     if (!main_frame->GetSuddenTerminationDisablerState(
             blink::mojom::SuddenTerminationDisablerType::
                 kBeforeUnloadHandler) &&
         render_process_host->FastShutdownIfPossible(
-            1u, /*skip_unload_handlers=*/true)) {
-      result = AttemptFastKillForDiscardResult::kKilledWithoutUnloadHandlers;
+            1u, /*skip_unload_handlers=*/true,
+            /*ignore_workers=*/should_ignore_workers)) {
+      result =
+          should_ignore_workers
+              ? AttemptFastKillForDiscardResult::
+                    kKilledWithoutUnloadHandlersAndWorkers
+              : AttemptFastKillForDiscardResult::kKilledWithoutUnloadHandlers;
     }
   }
 #endif
diff --git a/chrome/browser/resource_coordinator/utils.h b/chrome/browser/resource_coordinator/utils.h
index de1566c..56a156cb 100644
--- a/chrome/browser/resource_coordinator/utils.h
+++ b/chrome/browser/resource_coordinator/utils.h
@@ -15,6 +15,19 @@
 
 class TabLifecycleUnitSource;
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+//
+// LINT.IfChange(AttemptFastKillForDiscardResult)
+enum class AttemptFastKillForDiscardResult {
+  kKilled = 0,
+  kSkipped = 1,
+  kKilledWithoutUnloadHandlers = 2,
+  kKilledWithoutUnloadHandlersAndWorkers = 3,
+  kMaxValue = kKilledWithoutUnloadHandlersAndWorkers,
+};
+// LINT.ThenChange(//tools/metrics/histograms/metadata/tab/enums.xml:AttemptFastKillForDiscardResult)
+
 // Returns the TabLifecycleUnitSource indirectly owned by g_browser_process.
 TabLifecycleUnitSource* GetTabLifecycleUnitSource();
 
diff --git a/chrome/browser/resources/contextual_tasks/composebox.css b/chrome/browser/resources/contextual_tasks/composebox.css
index 8b35e2c..6a8752a9 100644
--- a/chrome/browser/resources/contextual_tasks/composebox.css
+++ b/chrome/browser/resources/contextual_tasks/composebox.css
@@ -142,18 +142,6 @@
   pointer-events: none;
 }
 
-#composeboxFooter {
-  height: 0px;
-  transition: height var(--emphasized-curve);
-  transition-duration: var(--collapse-duration);
-}
-
-#composeboxContainer:has(#composebox[expanding_]) #composeboxFooter {
-  height: 64px;
-  transition-duration: var(--expand-duration);
-  width: var(--composebox-width);
-}
-
 #composebox::part(icon-container) {
   display: none;
 }
@@ -173,7 +161,6 @@
 
 #composebox::part(action-icon) {
   --cr-icon-button-hover-background-color: var(--color-composebox-hover);
-  --cr-icon-button-size: 48px;
 }
 
 #composebox::part(cancel-icon) {
@@ -192,16 +179,11 @@
   pointer-events: none;
 }
 
-#composebox::part(submit) {
-  --cr-composebox-submit-container-size: 52px;
-  --cr-icon-button-size: 48px;
-  display: block;
-}
-
 #composebox::part(label) {
   display: none;
 }
 
+/* TODO(crbug.com/454388827: - Color the tab chips to spec. */
 #composebox::part(context-entrypoint) {
   transition-delay: 0ms;
 }
diff --git a/chrome/browser/resources/contextual_tasks/composebox.html.ts b/chrome/browser/resources/contextual_tasks/composebox.html.ts
index 9393259..fa5f18b 100644
--- a/chrome/browser/resources/contextual_tasks/composebox.html.ts
+++ b/chrome/browser/resources/contextual_tasks/composebox.html.ts
@@ -16,17 +16,13 @@
       --composebox-height: ${this.composeboxHeight_}px;
       --composebox-dropdown-height: ${this.composeboxDropdownHeight_}px;"
       >
+    <!-- TODO(crbug.com/454388827): isCollapsible should be false. -->
     <cr-composebox
       id="composebox"
       ?autofocus="${false}"
       carousel-on-top_
-      .isCollapsible="${true}"
       lens-button-disabled_$="${false}"
     >
-        <!-- Currently an empty div that has height to force the input textarea to
-            not flow behind the Lens icon. Eventually, this should contain the
-            submit button and the Lens icon. -->
-      <div id="composeboxFooter" slot="footer"></div>
     </cr-composebox>
   </div>
   <!--_html_template_end_-->`;
diff --git a/chrome/browser/resources/contextual_tasks/composebox.ts b/chrome/browser/resources/contextual_tasks/composebox.ts
index ad32ee66..62947341 100644
--- a/chrome/browser/resources/contextual_tasks/composebox.ts
+++ b/chrome/browser/resources/contextual_tasks/composebox.ts
@@ -8,6 +8,7 @@
 import {GlowAnimationState} from '//resources/cr_components/search/constants.js';
 import {EventTracker} from '//resources/js/event_tracker.js';
 import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
+import {loadTimeData} from '//resources/js/load_time_data.js';
 
 import {getCss} from './composebox.css.js';
 import {getHtml} from './composebox.html.js';
@@ -32,12 +33,18 @@
       composeboxHeight_: {type: Number},
       composeboxDropdownHeight_: {type: Number},
       isComposeboxFocused: {type: Boolean, reflect: true},
+      showContextMenu: {
+        reflect: true,
+        type: Boolean,
+        value: loadTimeData.getBoolean('composeboxShowContextMenu'),
+      },
     };
   }
 
   protected accessor composeboxHeight_: number = 0;
   protected accessor composeboxDropdownHeight_: number = 0;
   protected accessor isComposeboxFocused: boolean = false;
+  protected accessor showContextMenu: boolean = loadTimeData.getBoolean('composeboxShowContextMenu');
   private eventTracker_: EventTracker = new EventTracker();
   private composeboxResizeObserver_: ResizeObserver|null = null;
   private composeboxDropdownResizeObserver_: ResizeObserver|null = null;
@@ -54,6 +61,11 @@
         this.isComposeboxFocused = false;
         composebox.animationState = GlowAnimationState.NONE;
       });
+      this.eventTracker_.add(composebox, 'composebox-submit', () => {
+        // Clear the composebox text after submitting.
+        composebox.clearInput();
+        composebox.clearAutocompleteMatches();
+      });
 
       this.composeboxResizeObserver_ = new ResizeObserver(() => {
         this.composeboxHeight_ = composebox.offsetHeight;
diff --git a/chrome/browser/resources/side_panel/read_anything/read_aloud/ts_model_impl.ts b/chrome/browser/resources/side_panel/read_anything/read_aloud/ts_model_impl.ts
index 73b6a69f..3b622518 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_aloud/ts_model_impl.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_aloud/ts_model_impl.ts
@@ -21,7 +21,7 @@
 
   getHighlightForCurrentSegmentIndex(index: number, phrases: boolean):
       Segment[] {
-    if (this.currentIndex_ < 0 || !this.sentences_[this.currentIndex_]) {
+    if (this.currentIndex_ < 0 || !this.sentences_[this.currentIndex_] || index < 0) {
       return [];
     }
     const currentSentence = this.sentences_[this.currentIndex_]!;
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader.cc b/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader.cc
index 6e7e4a9..026c354 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader.cc
@@ -52,6 +52,8 @@
 constexpr char kUploadContentType[] = "application/octet-stream";
 // Content type of metadata.
 constexpr char kMetadataContentType[] = "application/json";
+// Content type of pasted images.
+constexpr char kImageContentType[] = "image/png";
 
 std::unique_ptr<ConnectorDataPipeGetter> CreateFileDataPipeGetterBlocking(
     const base::FilePath& path,
@@ -163,9 +165,10 @@
   request->headers.SetHeader(kUploadCommandHeader, "start");
   request->headers.SetHeader(kUploadHeaderContentLengthHeader,
                              base::NumberToString(data_size_));
-  request->headers.SetHeader(kUploadHeaderContentTypeHeader,
-                             kUploadContentType);
-
+  // `STRING` is only used for resumable requests for image pasting.
+  request->headers.SetHeader(
+      kUploadHeaderContentTypeHeader,
+      data_source_ == STRING ? kImageContentType : kUploadContentType);
   if (!access_token_.empty()) {
     LogAuthenticatedCookieResets(
         *request, SafeBrowsingAuthenticatedEndpoint::kDeepScanning);
@@ -440,7 +443,7 @@
   url_loader_->SetAllowHttpErrorResults(true);
 
   if (!data_pipe_getter_) {
-    url_loader_->AttachStringForUpload(data_, kUploadContentType);
+    url_loader_->AttachStringForUpload(data_, kImageContentType);
   }
 
   url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader_unittest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader_unittest.cc
index fe80cbe1..75e27dbc 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader_unittest.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader_unittest.cc
@@ -158,7 +158,8 @@
 
   void VerifyMetadataRequestHeaders(
       const network::ResourceRequest& resource_request,
-      std::string expected_size) {
+      std::string expected_size,
+      const std::string& expected_content_type = "application/octet-stream") {
     ASSERT_TRUE(resource_request.headers.HasHeader("X-Goog-Upload-Protocol"));
     ASSERT_THAT(resource_request.headers.GetHeader("X-Goog-Upload-Protocol"),
                 testing::Optional(std::string("resumable")));
@@ -171,7 +172,7 @@
         "X-Goog-Upload-Header-Content-Type"));
     ASSERT_THAT(
         resource_request.headers.GetHeader("X-Goog-Upload-Header-Content-Type"),
-        testing::Optional(std::string("application/octet-stream")));
+        testing::Optional(expected_content_type));
 
     ASSERT_TRUE(resource_request.headers.HasHeader(
         "X-Goog-Upload-Header-Content-Length"));
@@ -262,7 +263,7 @@
   auto* request = static_cast<ResumableUploadRequest*>(connector_request.get());
   request->SetMetadataRequestHeaders(&resource_request);
 
-  VerifyMetadataRequestHeaders(std::move(resource_request), "11");
+  VerifyMetadataRequestHeaders(std::move(resource_request), "11", "image/png");
 }
 
 class ResumableUploadSendMetadataRequestTest
diff --git a/chrome/browser/save_to_drive/save_to_drive_flow_browsertest.cc b/chrome/browser/save_to_drive/save_to_drive_flow_browsertest.cc
index 822f6dd..231d1be8 100644
--- a/chrome/browser/save_to_drive/save_to_drive_flow_browsertest.cc
+++ b/chrome/browser/save_to_drive/save_to_drive_flow_browsertest.cc
@@ -47,14 +47,12 @@
 namespace {
 
 AccountInfo CreateAccountInfo(bool is_managed) {
-  AccountInfo account_info;
-  account_info.email = "test@mail.com";
-  account_info.gaia = GaiaId("1234567890");
-  account_info.account_id = CoreAccountId::FromGaiaId(account_info.gaia);
+  AccountInfo::Builder builder(GaiaId("123456789"), "test@mail.com");
+  builder.SetAccountId(CoreAccountId::FromGaiaId(GaiaId("123456789")));
   if (is_managed) {
-    account_info.hosted_domain = "mail.com";
+    builder.SetHostedDomain("mail.com");
   }
-  return account_info;
+  return builder.Build();
 }
 
 }  // namespace
@@ -242,7 +240,8 @@
 
 IN_PROC_BROWSER_TEST_P(SaveToDriveFlowBrowserTest, ContentReadFails) {
   AccountInfo account_info = CreateAccountInfo(/*is_managed=*/false);
-  account_info.hosted_domain = "example.com";
+  account_info =
+      AccountInfo::Builder(account_info).SetHostedDomain("example.com").Build();
   SimulateAccountChooserAction(std::move(account_info));
   EXPECT_CALL(event_dispatcher(),
               Notify(AllOf(Field(&SaveToDriveProgress::status,
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
index eb28ac5b..3060b88 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
+++ b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
@@ -257,6 +257,10 @@
       search_engines::kSearchEngineChoiceScreenEventsHistogram,
       search_engines::SearchEngineChoiceScreenEvents::kLearnMoreWasDisplayed,
       1);
+  histogram_tester().ExpectBucketCount(
+      search_engines::kPumaSearchChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::kLearnMoreWasDisplayed,
+      1);
 
   search_engine_choice_dialog_service->NotifyLearnMoreLinkClicked(
       SearchEngineChoiceDialogService::EntryPoint::kFirstRunExperience);
@@ -264,6 +268,10 @@
       search_engines::kSearchEngineChoiceScreenEventsHistogram,
       search_engines::SearchEngineChoiceScreenEvents::kFreLearnMoreWasDisplayed,
       1);
+  histogram_tester().ExpectBucketCount(
+      search_engines::kPumaSearchChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::kFreLearnMoreWasDisplayed,
+      1);
 
   search_engine_choice_dialog_service->NotifyLearnMoreLinkClicked(
       SearchEngineChoiceDialogService::EntryPoint::kProfileCreation);
@@ -272,6 +280,11 @@
       search_engines::SearchEngineChoiceScreenEvents::
           kProfileCreationLearnMoreDisplayed,
       1);
+  histogram_tester().ExpectBucketCount(
+      search_engines::kPumaSearchChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::
+          kProfileCreationLearnMoreDisplayed,
+      1);
 }
 
 TEST_F(SearchEngineChoiceDialogServiceTest, NotifyMoreButtonClicked) {
@@ -284,12 +297,20 @@
       search_engines::kSearchEngineChoiceScreenEventsHistogram,
       search_engines::SearchEngineChoiceScreenEvents::kMoreButtonClicked, 1);
 
+  histogram_tester().ExpectBucketCount(
+      search_engines::kPumaSearchChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::kMoreButtonClicked, 1);
+
   search_engine_choice_dialog_service->NotifyMoreButtonClicked(
       SearchEngineChoiceDialogService::EntryPoint::kFirstRunExperience);
   histogram_tester().ExpectBucketCount(
       search_engines::kSearchEngineChoiceScreenEventsHistogram,
       search_engines::SearchEngineChoiceScreenEvents::kFreMoreButtonClicked, 1);
 
+  histogram_tester().ExpectBucketCount(
+      search_engines::kPumaSearchChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::kFreMoreButtonClicked, 1);
+
   search_engine_choice_dialog_service->NotifyMoreButtonClicked(
       SearchEngineChoiceDialogService::EntryPoint::kProfileCreation);
   histogram_tester().ExpectBucketCount(
@@ -297,6 +318,11 @@
       search_engines::SearchEngineChoiceScreenEvents::
           kProfileCreationMoreButtonClicked,
       1);
+  histogram_tester().ExpectBucketCount(
+      search_engines::kPumaSearchChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::
+          kProfileCreationMoreButtonClicked,
+      1);
 }
 
 TEST_F(SearchEngineChoiceDialogServiceTest,
@@ -324,6 +350,10 @@
       search_engines::kSearchEngineChoiceScreenEventsHistogram,
       search_engines::SearchEngineChoiceScreenEvents::kChoiceScreenWasDisplayed,
       1);
+  histogram_tester().ExpectUniqueSample(
+      search_engines::kPumaSearchChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::kChoiceScreenWasDisplayed,
+      1);
 
   EXPECT_EQ(
       user_action_tester().GetActionCount("SearchEngineChoiceScreenShown"), 1);
@@ -340,6 +370,9 @@
   histogram_tester().ExpectBucketCount(
       search_engines::kSearchEngineChoiceScreenEventsHistogram,
       search_engines::SearchEngineChoiceScreenEvents::kDefaultWasSet, 1);
+  histogram_tester().ExpectBucketCount(
+      search_engines::kPumaSearchChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::kDefaultWasSet, 1);
   // Recorded when we call `SetUserSelectedDefaultSearchProvider()`.
   histogram_tester().ExpectUniqueSample(
       search_engines::kSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
@@ -357,6 +390,9 @@
   histogram_tester().ExpectBucketCount(
       search_engines::kSearchEngineChoiceScreenEventsHistogram,
       search_engines::SearchEngineChoiceScreenEvents::kFreDefaultWasSet, 1);
+  histogram_tester().ExpectBucketCount(
+      search_engines::kPumaSearchChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::kFreDefaultWasSet, 1);
   histogram_tester().ExpectUniqueSample(
       search_engines::kSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
       SearchEngineType::SEARCH_ENGINE_GOOGLE, 1);
@@ -375,6 +411,11 @@
       search_engines::SearchEngineChoiceScreenEvents::
           kProfileCreationDefaultWasSet,
       1);
+  histogram_tester().ExpectBucketCount(
+      search_engines::kPumaSearchChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::
+          kProfileCreationDefaultWasSet,
+      1);
   histogram_tester().ExpectUniqueSample(
       search_engines::kSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
       SearchEngineType::SEARCH_ENGINE_GOOGLE, 1);
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/ExtensionParentApproval.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/ExtensionParentApproval.java
index 6b1b278..3afcb6f 100644
--- a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/ExtensionParentApproval.java
+++ b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/ExtensionParentApproval.java
@@ -34,20 +34,14 @@
      * @param windowAndroid The window to which the approval UI should be attached.
      */
     @CalledByNative
-    private static void requestExtensionApproval(
-            @SuppressWarnings("unused") WindowAndroid windowAndroid) {
+    private static void requestExtensionApproval(WindowAndroid windowAndroid) {
         ParentAuthDelegate delegate = ParentAuthDelegateProvider.getInstance();
         assert delegate != null;
-
-        // TODO(crbug.com/452265525): Enable this call in the cleanup CL.
-        // delegate.requestExtensionAuth(
-        //         windowAndroid,
-        //         (success) -> {
-        //             onParentAuthComplete(success);
-        //         });
-
-        // TEMPORARY STUB: Immediately call completion callback.
-        onParentAuthComplete(false);
+        delegate.requestExtensionAuth(
+                windowAndroid,
+                (success) -> {
+                    onParentAuthComplete(success);
+                });
     }
 
     private static void onParentAuthComplete(boolean success) {
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/ParentAuthDelegate.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/ParentAuthDelegate.java
index bcbedcd2..f27004e6 100644
--- a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/ParentAuthDelegate.java
+++ b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/ParentAuthDelegate.java
@@ -30,13 +30,5 @@
     /**
      * @see {@link ExtensionParentApproval#requestExtensionApproval()}
      */
-    // TODO(crbug.com/452265525): Remove this default implementation in the cleanup CL.
-    default void requestExtensionAuth(
-            @SuppressWarnings("unused") WindowAndroid windowAndroid,
-            Callback<Boolean> onCompletionCallback) {
-        // SHIM: Default implementation to prevent breakage before Clank roll.
-        if (onCompletionCallback != null) {
-            onCompletionCallback.onResult(false);
-        }
-    }
+    void requestExtensionAuth(WindowAndroid windowAndroid, Callback<Boolean> onCompletionCallback);
 }
diff --git a/chrome/browser/supervised_user/supervised_user_extensions_delegate_impl.cc b/chrome/browser/supervised_user/supervised_user_extensions_delegate_impl.cc
index de9fb36..722550f 100644
--- a/chrome/browser/supervised_user/supervised_user_extensions_delegate_impl.cc
+++ b/chrome/browser/supervised_user/supervised_user_extensions_delegate_impl.cc
@@ -24,6 +24,10 @@
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/native_ui_types.h"
 
+#if BUILDFLAG(IS_ANDROID)
+#include "chrome/browser/supervised_user/android/extension_parent_approval.h"
+#endif  // BUILDFLAG(IS_ANDROID)
+
 namespace {
 
 void OnParentPermissionDialogComplete(
@@ -208,6 +212,10 @@
                              : ParentAccessExtensionApprovalsManager::
                                    ExtensionInstallMode::kInstallationDenied,
       std::move(done_callback_));
+#elif BUILDFLAG(IS_ANDROID)
+  CHECK(contents.value());
+  ExtensionParentApproval::RequestExtensionApproval(contents.value().get(),
+                                                    std::move(done_callback_));
 #endif
 }
 
diff --git a/chrome/browser/sync/test/integration/enable_disable_test.cc b/chrome/browser/sync/test/integration/enable_disable_test.cc
index a9aaace9..54acfbd 100644
--- a/chrome/browser/sync/test/integration/enable_disable_test.cc
+++ b/chrome/browser/sync/test/integration/enable_disable_test.cc
@@ -118,9 +118,19 @@
 
 // This test enables and disables types and verifies the type is active via
 // SyncService::GetActiveDataTypes().
-class EnableDisableSingleClientTest : public SyncTest {
+class EnableDisableSingleClientTest
+    : public SyncTest,
+      public testing::WithParamInterface<SyncTest::SetupSyncMode> {
  public:
-  EnableDisableSingleClientTest() : SyncTest(SINGLE_CLIENT) {}
+  EnableDisableSingleClientTest() : SyncTest(SINGLE_CLIENT) {
+    if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+      scoped_feature_list_.InitWithFeatures(
+          /*enabled_features=*/
+          {syncer::kReplaceSyncPromosWithSignInPromos,
+           syncer::kSpellcheckSeparateLocalAndAccountDictionaries},
+          /*disabled_features=*/{});
+    }
+  }
 
   EnableDisableSingleClientTest(const EnableDisableSingleClientTest&) = delete;
   EnableDisableSingleClientTest& operator=(
@@ -128,6 +138,10 @@
 
   ~EnableDisableSingleClientTest() override = default;
 
+  SyncTest::SetupSyncMode GetSetupSyncMode() const override {
+    return GetParam();
+  }
+
   // Don't use self-notifications as they can trigger additional sync cycles.
   bool TestUsesSelfNotifications() override { return false; }
 
@@ -164,15 +178,23 @@
                                           true);
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
-    ASSERT_TRUE(
-        GetClient(0)->SetupSyncWithCustomSettings(base::BindLambdaForTesting(
-            [all_types_enabled](syncer::SyncUserSettings* user_settings) {
-              user_settings->SetSelectedTypes(all_types_enabled, {});
+    if (GetSetupSyncMode() == SyncTest::SetupSyncMode::kSyncTheFeature) {
+      ASSERT_TRUE(GetClient(0)->SetupSyncWithCustomSettings(
+          base::BindLambdaForTesting([all_types_enabled](
+                                         syncer::SyncUserSettings*
+                                             user_settings) {
+            user_settings->SetSelectedTypes(all_types_enabled, {});
 #if !BUILDFLAG(IS_CHROMEOS)
-              user_settings->SetInitialSyncFeatureSetupComplete(
-                  syncer::SyncFirstSetupCompleteSource::ADVANCED_FLOW_CONFIRM);
+            user_settings->SetInitialSyncFeatureSetupComplete(
+                syncer::SyncFirstSetupCompleteSource::ADVANCED_FLOW_CONFIRM);
 #endif  // !BUILDFLAG(IS_CHROMEOS)
-            })));
+          })));
+    } else {
+      ASSERT_TRUE(GetClient(0)->SignInPrimaryAccount());
+      GetSyncService(0)->GetUserSettings()->SetSelectedTypes(all_types_enabled,
+                                                             {});
+      ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
+    }
 
     registered_data_types_ = GetSyncService(0)->GetRegisteredDataTypesForTest();
 
@@ -190,15 +212,33 @@
     return Difference(input, multi_grouped_types_);
   }
 
+  DataTypeSet UnsupportedTypes() const {
+    if (GetSetupSyncMode() == SyncTest::SetupSyncMode::kSyncTheFeature) {
+      return {};
+    }
+    // Some data types are intentionally not supported in transport mode.
+    // TODO(crbug.com/40066949): Simplify (fully removes these types) once
+    // Sync-the-feature is gone.
+    return {syncer::AUTOFILL, syncer::AUTOFILL_PROFILE, syncer::APPS,
+            syncer::APP_SETTINGS};
+  }
+
   DataTypeSet registered_data_types_;
   DataTypeSet multi_grouped_types_;
   UserSelectableTypeSet registered_selectable_types_;
 
  private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
   fake_server::EntityBuilderFactory entity_builder_factory_;
 };
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientTest, PRE_EnableAndRestart) {
+INSTANTIATE_TEST_SUITE_P(,
+                         EnableDisableSingleClientTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientTest, PRE_EnableAndRestart) {
   GetUpdatesRequestRecorder get_updates_recorder(GetFakeServer());
 
   SetupTest(/*all_types_enabled=*/true);
@@ -208,9 +248,11 @@
   // isn't supervised. Finally, a few types aren't launched so they should also
   // be excluded.
   const DataTypeSet types_without_updates =
-      Union(syncer::CommitOnlyTypes(),
-            {syncer::SUPERVISED_USER_SETTINGS, syncer::PLUS_ADDRESS,
-             syncer::PLUS_ADDRESS_SETTING});
+      Union(Union(syncer::CommitOnlyTypes(),
+                  {syncer::SUPERVISED_USER_SETTINGS, syncer::PLUS_ADDRESS,
+                   syncer::PLUS_ADDRESS_SETTING}),
+            UnsupportedTypes());
+
   // High priority types in this test are a subset of
   // syncer::HighPriorityUserTypes(), excluding those identified earlier.
   const DataTypeSet high_priority_types = Difference(
@@ -251,7 +293,7 @@
                       Union(syncer::ControlTypes(), low_priority_types))));
 }
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientTest, EnableAndRestart) {
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientTest, EnableAndRestart) {
   GetUpdatesRequestRecorder get_updates_recorder(GetFakeServer());
 
   ASSERT_TRUE(SetupClients());
@@ -261,14 +303,15 @@
   for (UserSelectableType type : UserSelectableTypeSet::All()) {
     for (DataType data_type : ResolveGroup(type)) {
       EXPECT_TRUE(IsDataTypeActive(data_type))
-          << " for " << DataTypeToDebugString(data_type);
+          << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(data_type);
     }
   }
 
   EXPECT_THAT(get_updates_recorder.recorded_requests(), IsEmpty());
 }
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientTest, EnableOneAtATime) {
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientTest, EnableOneAtATime) {
   // Setup sync with no enabled types.
   SetupTest(/*all_types_enabled=*/false);
 
@@ -283,15 +326,21 @@
     const DataTypeSet grouped_types = ResolveGroup(type);
     for (DataType single_grouped_type : WithoutMultiTypes(grouped_types)) {
       ASSERT_FALSE(IsDataTypeActive(single_grouped_type))
-          << " for " << GetUserSelectableTypeName(type);
+          << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(single_grouped_type);
     }
 
     base::HistogramTester histogram_tester;
-    EXPECT_TRUE(GetClient(0)->EnableSyncForType(type));
+    EXPECT_TRUE(GetClient(0)->EnableSelectableType(type));
 
     for (DataType grouped_type : grouped_types) {
+      if (UnsupportedTypes().Has(grouped_type)) {
+        continue;
+      }
+
       EXPECT_TRUE(IsDataTypeActive(grouped_type))
-          << " for " << GetUserSelectableTypeName(type);
+          << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(grouped_type);
 
       if (!syncer::ProtocolTypes().Has(grouped_type) ||
           syncer::CommitOnlyTypes().Has(grouped_type)) {
@@ -299,7 +348,8 @@
                   histogram_tester.GetBucketCount(
                       "Sync.PostedDataTypeGetUpdatesRequest",
                       static_cast<int>(DataTypeHistogramValue(grouped_type))))
-            << " for " << DataTypeToDebugString(grouped_type);
+            << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+            << DataTypeToDebugString(grouped_type);
       } else if (previously_active_types.Has(grouped_type)) {
         // If the type was already configured, no additional configuration cycle
         // is expected, but it's impossible to rule out that the type has issued
@@ -310,7 +360,8 @@
                   histogram_tester.GetBucketCount(
                       "Sync.PostedDataTypeGetUpdatesRequest",
                       static_cast<int>(DataTypeHistogramValue(grouped_type))))
-            << " for " << DataTypeToDebugString(grouped_type);
+            << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+            << DataTypeToDebugString(grouped_type);
       }
 
       previously_active_types.Put(grouped_type);
@@ -318,22 +369,27 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientTest, DisableOneAtATime) {
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientTest, DisableOneAtATime) {
   // Setup sync with no disabled types.
   SetupTest(/*all_types_enabled=*/true);
 
   for (UserSelectableType type : registered_selectable_types_) {
     const DataTypeSet grouped_types = ResolveGroup(type);
     for (DataType grouped_type : grouped_types) {
+      if (UnsupportedTypes().Has(grouped_type)) {
+        continue;
+      }
       ASSERT_TRUE(IsDataTypeActive(grouped_type))
-          << " for " << GetUserSelectableTypeName(type);
+          << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(grouped_type);
     }
 
-    EXPECT_TRUE(GetClient(0)->DisableSyncForType(type));
+    EXPECT_TRUE(GetClient(0)->DisableSelectableType(type));
 
     for (DataType single_grouped_type : WithoutMultiTypes(grouped_types)) {
       EXPECT_FALSE(IsDataTypeActive(single_grouped_type))
-          << " for " << GetUserSelectableTypeName(type);
+          << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(single_grouped_type);
     }
   }
 
@@ -345,7 +401,7 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientTest,
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientTest,
                        FastEnableDisableOneAtATime) {
   // Setup sync with no enabled types.
   SetupTest(/*all_types_enabled=*/false);
@@ -355,17 +411,19 @@
     const DataTypeSet single_grouped_types = WithoutMultiTypes(grouped_types);
     for (DataType single_grouped_type : single_grouped_types) {
       ASSERT_FALSE(IsDataTypeActive(single_grouped_type))
-          << " for " << GetUserSelectableTypeName(type);
+          << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(single_grouped_type);
     }
 
     // Enable and then disable immediately afterwards, before the datatype has
     // had the chance to finish startup (which usually involves task posting).
-    EXPECT_TRUE(GetClient(0)->EnableSyncForType(type));
-    EXPECT_TRUE(GetClient(0)->DisableSyncForType(type));
+    EXPECT_TRUE(GetClient(0)->EnableSelectableType(type));
+    EXPECT_TRUE(GetClient(0)->DisableSelectableType(type));
 
     for (DataType single_grouped_type : single_grouped_types) {
       EXPECT_FALSE(IsDataTypeActive(single_grouped_type))
-          << " for " << GetUserSelectableTypeName(type);
+          << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(single_grouped_type);
     }
   }
 
@@ -377,7 +435,7 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientTest,
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientTest,
                        FastDisableEnableOneAtATime) {
   // Setup sync with no disabled types.
   SetupTest(/*all_types_enabled=*/true);
@@ -385,23 +443,31 @@
   for (UserSelectableType type : registered_selectable_types_) {
     const DataTypeSet grouped_types = ResolveGroup(type);
     for (DataType grouped_type : grouped_types) {
+      if (UnsupportedTypes().Has(grouped_type)) {
+        continue;
+      }
       ASSERT_TRUE(IsDataTypeActive(grouped_type))
-          << " for " << GetUserSelectableTypeName(type);
+          << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(grouped_type);
     }
 
     // Disable and then reenable immediately afterwards, before the datatype has
     // had the chance to stop fully (which usually involves task posting).
-    EXPECT_TRUE(GetClient(0)->DisableSyncForType(type));
-    EXPECT_TRUE(GetClient(0)->EnableSyncForType(type));
+    EXPECT_TRUE(GetClient(0)->DisableSelectableType(type));
+    EXPECT_TRUE(GetClient(0)->EnableSelectableType(type));
 
     for (DataType grouped_type : grouped_types) {
+      if (UnsupportedTypes().Has(grouped_type)) {
+        continue;
+      }
       EXPECT_TRUE(IsDataTypeActive(grouped_type))
-          << " for " << GetUserSelectableTypeName(type);
+          << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(grouped_type);
     }
   }
 }
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientTest,
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientTest,
                        FastEnableDisableEnableOneAtATime) {
   // Setup sync with no enabled types.
   SetupTest(/*all_types_enabled=*/false);
@@ -411,53 +477,63 @@
         WithoutMultiTypes(ResolveGroup(type));
     for (DataType single_grouped_type : single_grouped_types) {
       ASSERT_FALSE(IsDataTypeActive(single_grouped_type))
-          << " for " << GetUserSelectableTypeName(type);
+          << " for " << GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(single_grouped_type);
     }
 
     // Fast enable-disable-enable sequence, before the datatype has had the
     // chance to transition fully across states (usually involves task posting).
-    EXPECT_TRUE(GetClient(0)->EnableSyncForType(type));
-    EXPECT_TRUE(GetClient(0)->DisableSyncForType(type));
-    EXPECT_TRUE(GetClient(0)->EnableSyncForType(type));
+    EXPECT_TRUE(GetClient(0)->EnableSelectableType(type));
+    EXPECT_TRUE(GetClient(0)->DisableSelectableType(type));
+    EXPECT_TRUE(GetClient(0)->EnableSelectableType(type));
 
     for (DataType single_grouped_type : single_grouped_types) {
+      if (UnsupportedTypes().Has(single_grouped_type)) {
+        continue;
+      }
       EXPECT_TRUE(IsDataTypeActive(single_grouped_type))
-          << " for " << GetUserSelectableTypeName(type);
+          << " for " << GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(single_grouped_type);
     }
   }
 }
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientTest, EnableDisable) {
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientTest, EnableDisable) {
   SetupTest(/*all_types_enabled=*/false);
 
   // Enable all, and then disable immediately afterwards, before datatypes
   // have had the chance to finish startup (which usually involves task
   // posting).
-  ASSERT_TRUE(GetClient(0)->EnableSyncForRegisteredDatatypes());
-  ASSERT_TRUE(GetClient(0)->DisableSyncForAllDatatypes());
+  ASSERT_TRUE(GetClient(0)->EnableAllSelectableTypes());
+  ASSERT_TRUE(GetClient(0)->DisableAllSelectableTypes());
 
   for (UserSelectableType type : UserSelectableTypeSet::All()) {
     for (DataType grouped_type : ResolveGroup(type)) {
       EXPECT_FALSE(IsDataTypeActive(grouped_type))
-          << " for " << GetUserSelectableTypeName(type);
+          << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(grouped_type);
     }
   }
 }
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientTest, FastEnableDisableEnable) {
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientTest, FastEnableDisableEnable) {
   SetupTest(/*all_types_enabled=*/false);
 
   // Enable all, and then disable+reenable immediately afterwards, before
   // datatypes have had the chance to finish startup (which usually involves
   // task posting).
-  ASSERT_TRUE(GetClient(0)->EnableSyncForRegisteredDatatypes());
-  ASSERT_TRUE(GetClient(0)->DisableSyncForAllDatatypes());
-  ASSERT_TRUE(GetClient(0)->EnableSyncForRegisteredDatatypes());
+  ASSERT_TRUE(GetClient(0)->EnableAllSelectableTypes());
+  ASSERT_TRUE(GetClient(0)->DisableAllSelectableTypes());
+  ASSERT_TRUE(GetClient(0)->EnableAllSelectableTypes());
 
   for (UserSelectableType type : UserSelectableTypeSet::All()) {
     for (DataType data_type : ResolveGroup(type)) {
+      if (UnsupportedTypes().Has(data_type)) {
+        continue;
+      }
       EXPECT_TRUE(IsDataTypeActive(data_type))
-          << " for " << DataTypeToDebugString(data_type);
+          << " for " << syncer::GetUserSelectableTypeName(type) << "-"
+          << DataTypeToDebugString(data_type);
     }
   }
 }
@@ -469,7 +545,7 @@
 //
 // ChromeOS does not support signing out of a primary account.
 #if !BUILDFLAG(IS_CHROMEOS)
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientTest, RedownloadsAfterSignout) {
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientTest, RedownloadsAfterSignout) {
   ASSERT_TRUE(SetupClients());
   ASSERT_FALSE(bookmarks_helper::GetBookmarkModel(0)->IsBookmarked(
       GURL(kSyncedBookmarkURL)));
@@ -518,7 +594,7 @@
 }
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientTest,
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientTest,
                        DoesNotRedownloadAfterSyncUnpaused) {
   ASSERT_TRUE(SetupClients());
   ASSERT_FALSE(bookmarks_helper::GetBookmarkModel(0)->IsBookmarked(
@@ -556,13 +632,23 @@
   ASSERT_GT(GetNumUpdatesDownloadedInLastCycle(), 0);
 
   // Pause sync.
-  GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
-  ASSERT_FALSE(GetSyncService(0)->IsSyncFeatureActive());
+  if (GetSetupSyncMode() == SyncTest::SetupSyncMode::kSyncTheFeature) {
+    GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
+  } else {
+    GetClient(0)->EnterSignInPendingStateForPrimaryAccount();
+  }
+  ASSERT_EQ(GetSyncService(0)->GetTransportState(),
+            syncer::SyncService::TransportState::PAUSED);
 
   // Resume sync.
   base::HistogramTester histogram_tester;
-  GetClient(0)->ExitSyncPausedStateForPrimaryAccount();
-  ASSERT_TRUE(GetSyncService(0)->IsSyncFeatureActive());
+  if (GetSetupSyncMode() == SyncTest::SetupSyncMode::kSyncTheFeature) {
+    GetClient(0)->ExitSyncPausedStateForPrimaryAccount();
+  } else {
+    GetClient(0)->ExitSignInPendingStateForPrimaryAccount();
+  }
+  ASSERT_EQ(GetSyncService(0)->GetTransportState(),
+            syncer::SyncService::TransportState::ACTIVE);
 
   // The bookmark should still be there, *without* having been redownloaded.
   ASSERT_TRUE(SetupSync());
@@ -576,7 +662,7 @@
                    syncer::DataTypeEntityChange::kRemoteInitialUpdate));
 }
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientTest,
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientTest,
                        DoesNotClearPrefsWithKeepData) {
   SetupTest(/*all_types_enabled=*/true);
 
@@ -586,7 +672,11 @@
   const std::string cache_guid = prefs.GetCacheGuid();
   ASSERT_NE("", cache_guid);
 
-  GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
+  if (GetSetupSyncMode() == SyncTest::SetupSyncMode::kSyncTheFeature) {
+    GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
+  } else {
+    GetClient(0)->EnterSignInPendingStateForPrimaryAccount();
+  }
   EXPECT_EQ(cache_guid, prefs.GetCacheGuid());
 }
 
@@ -606,7 +696,12 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientSelfNotifyTest,
+INSTANTIATE_TEST_SUITE_P(,
+                         EnableDisableSingleClientSelfNotifyTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientSelfNotifyTest,
                        PRE_ResendsBagOfChips) {
   sync_pb::ChipBag bag_of_chips;
   bag_of_chips.set_server_chips(kTestServerChips);
@@ -625,7 +720,7 @@
   EXPECT_EQ(kTestServerChips, message.bag_of_chips().server_chips());
 }
 
-IN_PROC_BROWSER_TEST_F(EnableDisableSingleClientSelfNotifyTest,
+IN_PROC_BROWSER_TEST_P(EnableDisableSingleClientSelfNotifyTest,
                        ResendsBagOfChips) {
   ASSERT_TRUE(SetupClients());
   syncer::SyncTransportDataPrefs prefs(
diff --git a/chrome/browser/sync/test/integration/single_client_extension_apps_sync_test.cc b/chrome/browser/sync/test/integration/single_client_extension_apps_sync_test.cc
index c6bbcf5..c117213 100644
--- a/chrome/browser/sync/test/integration/single_client_extension_apps_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_extension_apps_sync_test.cc
@@ -84,6 +84,11 @@
   SingleClientExtensionAppsSyncTest() : SyncTest(SINGLE_CLIENT) {}
   ~SingleClientExtensionAppsSyncTest() override = default;
 
+  // Apps sync is only supported with Sync-the-feature.
+  SetupSyncMode GetSetupSyncMode() const override {
+    return SetupSyncMode::kSyncTheFeature;
+  }
+
  private:
 #if BUILDFLAG(IS_WIN)
   // This stops extension installation from creating a shortcut in the real
diff --git a/chrome/browser/sync/test/integration/single_client_offer_sync_test.cc b/chrome/browser/sync/test/integration/single_client_offer_sync_test.cc
index 01591c8..0218f5d5 100644
--- a/chrome/browser/sync/test/integration/single_client_offer_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_offer_sync_test.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/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/sync/test/integration/autofill_helper.h"
 #include "chrome/browser/sync/test/integration/offer_helper.h"
@@ -13,6 +14,7 @@
 #include "components/autofill/core/browser/data_model/payments/autofill_offer_data.h"
 #include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
 #include "components/sync/base/data_type.h"
+#include "components/sync/base/features.h"
 #include "components/sync/protocol/data_type_state.pb.h"
 #include "components/sync/service/sync_service.h"
 #include "components/sync/test/fake_server.h"
@@ -37,9 +39,16 @@
 
 }  // namespace
 
-class SingleClientOfferSyncTest : public SyncTest {
+class SingleClientOfferSyncTest
+    : public SyncTest,
+      public testing::WithParamInterface<SyncTest::SetupSyncMode> {
  public:
-  SingleClientOfferSyncTest() : SyncTest(SINGLE_CLIENT) {}
+  SingleClientOfferSyncTest() : SyncTest(SINGLE_CLIENT) {
+    if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+      scoped_feature_list_.InitAndEnableFeature(
+          syncer::kReplaceSyncPromosWithSignInPromos);
+    }
+  }
 
   ~SingleClientOfferSyncTest() override = default;
 
@@ -47,6 +56,10 @@
   SingleClientOfferSyncTest& operator=(const SingleClientOfferSyncTest&) =
       delete;
 
+  SyncTest::SetupSyncMode GetSetupSyncMode() const override {
+    return GetParam();
+  }
+
  protected:
   void WaitForNumberOfOffers(size_t expected_count,
                              autofill::PaymentsDataManager* paydm) {
@@ -64,10 +77,18 @@
                                                syncer::AUTOFILL_WALLET_OFFER)
         .Wait();
   }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
+INSTANTIATE_TEST_SUITE_P(
+    /* no prefix */,
+    SingleClientOfferSyncTest,
+    GetSyncTestModes(),
+    testing::PrintToStringParamName());
+
 // Ensures that the offer sync type is enabled by default.
-IN_PROC_BROWSER_TEST_F(SingleClientOfferSyncTest, EnabledByDefault) {
+IN_PROC_BROWSER_TEST_P(SingleClientOfferSyncTest, EnabledByDefault) {
   ASSERT_TRUE(SetupSync());
   ASSERT_TRUE(GetClient(0)->service()->GetActiveDataTypes().Has(
       syncer::AUTOFILL_WALLET_OFFER));
@@ -79,7 +100,7 @@
 // Excluded on Android because SyncServiceImplHarness doesn't have the ability
 // to mimic sync-paused on Android due to https://crbug.com/1373448.
 #if !BUILDFLAG(IS_ANDROID)
-IN_PROC_BROWSER_TEST_F(SingleClientOfferSyncTest, ClearOnSyncPaused) {
+IN_PROC_BROWSER_TEST_P(SingleClientOfferSyncTest, ClearOnSyncPaused) {
   GetFakeServer()->SetOfferData({CreateDefaultSyncCardLinkedOffer()});
   ASSERT_TRUE(SetupSync());
 
@@ -89,12 +110,20 @@
   ASSERT_EQ(1uL, paydm->GetAutofillOffers().size());
 
   // Pause sync, the offer data should be gone.
-  GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
+  if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+    GetClient(0)->EnterSignInPendingStateForPrimaryAccount();
+  } else {
+    GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
+  }
   WaitForNumberOfOffers(0, paydm);
   EXPECT_EQ(0uL, paydm->GetAutofillOffers().size());
 
   // Resume (unpause) sync, the data should come back.
-  GetClient(0)->ExitSyncPausedStateForPrimaryAccount();
+  if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+    GetClient(0)->ExitSignInPendingStateForPrimaryAccount();
+  } else {
+    GetClient(0)->ExitSyncPausedStateForPrimaryAccount();
+  }
   // Wait until Sync restores the card and it arrives at paydm.
   WaitForNumberOfOffers(1, paydm);
   EXPECT_EQ(1uL, paydm->GetAutofillOffers().size());
@@ -104,7 +133,7 @@
 // ChromeOS does not sign out, so the test below does not apply.
 #if !BUILDFLAG(IS_CHROMEOS)
 // Offer data should get cleared from the database when the user signs out.
-IN_PROC_BROWSER_TEST_F(SingleClientOfferSyncTest, ClearOnSignOut) {
+IN_PROC_BROWSER_TEST_P(SingleClientOfferSyncTest, ClearOnSignOut) {
   GetFakeServer()->SetOfferData({CreateDefaultSyncCardLinkedOffer()});
   ASSERT_TRUE(SetupSync());
   autofill::PaymentsDataManager* paydm = GetPaymentsDataManager(0);
@@ -121,7 +150,7 @@
 
 // Offer is not using incremental updates. Make sure existing data gets
 // replaced when synced down.
-IN_PROC_BROWSER_TEST_F(SingleClientOfferSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientOfferSyncTest,
                        NewSyncDataShouldReplaceExistingData) {
   AutofillOfferData offer1 = GetCardLinkedOfferData1(/*offer_id=*/999);
   GetFakeServer()->SetOfferData({CreateSyncCardLinkedOffer(offer1)});
@@ -148,7 +177,7 @@
 // Offer is not using incremental updates. The server either sends a non-empty
 // update with deletion gc directives and with the (possibly empty) full data
 // set, or (more often) an empty update.
-IN_PROC_BROWSER_TEST_F(SingleClientOfferSyncTest, EmptyUpdatesAreIgnored) {
+IN_PROC_BROWSER_TEST_P(SingleClientOfferSyncTest, EmptyUpdatesAreIgnored) {
   AutofillOfferData offer1 = GetCardLinkedOfferData1(/*offer_id=*/999);
   GetFakeServer()->SetOfferData({CreateSyncCardLinkedOffer(offer1)});
   ASSERT_TRUE(SetupSync());
@@ -161,14 +190,14 @@
   EXPECT_EQ(999, offers[0]->GetOfferId());
 
   // Trigger a sync and wait for the new data to arrive.
-  sync_pb::DataTypeState state_before =
-      GetWalletDataTypeState(syncer::AUTOFILL_WALLET_OFFER, 0);
+  sync_pb::DataTypeState state_before = GetWalletDataTypeState(
+      syncer::AUTOFILL_WALLET_OFFER, 0, GetSetupSyncMode());
   ASSERT_TRUE(TriggerGetUpdatesAndWait());
 
   // Check that the new progress marker is stored for empty updates. This is a
   // regression check for crbug.com/924447.
-  sync_pb::DataTypeState state_after =
-      GetWalletDataTypeState(syncer::AUTOFILL_WALLET_OFFER, 0);
+  sync_pb::DataTypeState state_after = GetWalletDataTypeState(
+      syncer::AUTOFILL_WALLET_OFFER, 0, GetSetupSyncMode());
   EXPECT_NE(state_before.progress_marker().token(),
             state_after.progress_marker().token());
 
@@ -189,7 +218,7 @@
 
 // If the server sends the same offers with changed data, they should change on
 // the client.
-IN_PROC_BROWSER_TEST_F(SingleClientOfferSyncTest, ChangedEntityGetsUpdated) {
+IN_PROC_BROWSER_TEST_P(SingleClientOfferSyncTest, ChangedEntityGetsUpdated) {
   AutofillOfferData offer = GetCardLinkedOfferData1(/*offer_id=*/999);
   offer.SetEligibleInstrumentIdForTesting({111111});
   GetFakeServer()->SetOfferData({CreateSyncCardLinkedOffer(offer)});
@@ -219,7 +248,7 @@
 
 // Offer data should get cleared from the database when the Autofill sync type
 // flag is disabled.
-IN_PROC_BROWSER_TEST_F(SingleClientOfferSyncTest, ClearOnDisableWalletSync) {
+IN_PROC_BROWSER_TEST_P(SingleClientOfferSyncTest, ClearOnDisableWalletSync) {
   GetFakeServer()->SetOfferData({CreateDefaultSyncCardLinkedOffer()});
   ASSERT_TRUE(SetupSync());
 
diff --git a/chrome/browser/sync/test/integration/single_client_outgoing_password_sharing_invitation_test.cc b/chrome/browser/sync/test/integration/single_client_outgoing_password_sharing_invitation_test.cc
index 9ce5d86..2598787d 100644
--- a/chrome/browser/sync/test/integration/single_client_outgoing_password_sharing_invitation_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_outgoing_password_sharing_invitation_test.cc
@@ -85,10 +85,20 @@
   const size_t expected_entities_count_;
 };
 
-class SingleClientOutgoingPasswordSharingInvitationTest : public SyncTest {
+class SingleClientOutgoingPasswordSharingInvitationTest
+    : public SyncTest,
+      public testing::WithParamInterface<SyncTest::SetupSyncMode> {
  public:
   SingleClientOutgoingPasswordSharingInvitationTest()
       : SyncTest(SINGLE_CLIENT) {
+    if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+      scoped_feature_list_.InitAndEnableFeature(
+          syncer::kReplaceSyncPromosWithSignInPromos);
+    }
+  }
+
+  SyncTest::SetupSyncMode GetSetupSyncMode() const override {
+    return GetParam();
   }
 
   PasswordSenderService* GetPasswordSenderService() {
@@ -114,9 +124,17 @@
     DCHECK(nigori_specifics.has_cross_user_sharing_public_key());
     return nigori_specifics.cross_user_sharing_public_key();
   }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(SingleClientOutgoingPasswordSharingInvitationTest,
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientOutgoingPasswordSharingInvitationTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(SingleClientOutgoingPasswordSharingInvitationTest,
                        ShouldCommitSentPassword) {
   ASSERT_TRUE(SetupSync());
 
@@ -141,7 +159,7 @@
   EXPECT_EQ(specifics.recipient_key_version(), kRecipientPublicKeyVersion);
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientOutgoingPasswordSharingInvitationTest,
+IN_PROC_BROWSER_TEST_P(SingleClientOutgoingPasswordSharingInvitationTest,
                        ShouldCommitSentPasswordGroup) {
   ASSERT_TRUE(SetupSync());
 
@@ -203,29 +221,4 @@
               UnorderedElementsAre(kUsernameElement, "username_element_2"));
 }
 
-// The unconsented primary account isn't supported on ChromeOS.
-// TODO(crbug.com/358053884): enable on Android once transport mode for
-// Passwords is supported.
-#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID)
-IN_PROC_BROWSER_TEST_F(SingleClientOutgoingPasswordSharingInvitationTest,
-                       ShouldCommitSentPasswordInTransportMode) {
-  // First, setup sync (in transport mode) to initialize Nigori node with a
-  // cross user sharing key pair to be able to send passwords.
-  ASSERT_TRUE(SetupClients());
-  ASSERT_TRUE(GetClient(0)->SignInPrimaryAccount());
-  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
-  ASSERT_FALSE(GetSyncService(0)->IsSyncFeatureEnabled());
-  ASSERT_TRUE(password_manager::features_util::IsAccountStorageEnabled(
-      GetSyncService(0)));
-
-  PasswordRecipient recipient = {
-      .user_id = kRecipientUserId,
-      .public_key = PublicKey::FromProto(PublicKeyFromKeyPair(
-          syncer::CrossUserSharingPublicPrivateKeyPair::GenerateNewKeyPair()))};
-  GetPasswordSenderService()->SendPasswords({MakePasswordForm()}, recipient);
-
-  EXPECT_TRUE(InvitationCommittedChecker(/*expected_entities_count=*/1).Wait());
-}
-#endif  // !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID)
-
 }  // namespace
diff --git a/chrome/browser/sync/test/integration/single_client_password_sharing_policy_test.cc b/chrome/browser/sync/test/integration/single_client_password_sharing_policy_test.cc
index c323bb3..79b8a125 100644
--- a/chrome/browser/sync/test/integration/single_client_password_sharing_policy_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_password_sharing_policy_test.cc
@@ -17,12 +17,22 @@
 using policy::PolicyMap;
 using testing::NiceMock;
 
-class SingleClientPasswordSharingPolicyTest : public SyncTest {
+class SingleClientPasswordSharingPolicyTest
+    : public SyncTest,
+      public testing::WithParamInterface<SyncTest::SetupSyncMode> {
  public:
   SingleClientPasswordSharingPolicyTest() : SyncTest(SINGLE_CLIENT) {
+    if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+      scoped_feature_list_.InitAndEnableFeature(
+          syncer::kReplaceSyncPromosWithSignInPromos);
+    }
   }
   ~SingleClientPasswordSharingPolicyTest() override = default;
 
+  SyncTest::SetupSyncMode GetSetupSyncMode() const override {
+    return GetParam();
+  }
+
   void SetUpInProcessBrowserTestFixture() override {
     SyncTest::SetUpInProcessBrowserTestFixture();
     policy_provider_.SetDefaultReturns(
@@ -41,10 +51,16 @@
   }
 
  private:
+  base::test::ScopedFeatureList scoped_feature_list_;
   NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
 };
 
-IN_PROC_BROWSER_TEST_F(SingleClientPasswordSharingPolicyTest,
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientPasswordSharingPolicyTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(SingleClientPasswordSharingPolicyTest,
                        ShouldDisablePasswordSharingDataTypes) {
   ASSERT_TRUE(SetupSync());
 
diff --git a/chrome/browser/sync/test/integration/single_client_plus_address_sync_test.cc b/chrome/browser/sync/test/integration/single_client_plus_address_sync_test.cc
index 638db3cc..dff310b 100644
--- a/chrome/browser/sync/test/integration/single_client_plus_address_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_plus_address_sync_test.cc
@@ -78,40 +78,27 @@
       scoped_observation_{this};
 };
 
-// PLUS_ADDRESS is supposed to behave the same in and outside of transport mode.
-// These tests are parameterized by whether the test should run in transport
-// mode (true) or not (false).
 class SingleClientPlusAddressSyncTest
     : public SyncTest,
-      public testing::WithParamInterface<bool> {
+      public testing::WithParamInterface<SyncTest::SetupSyncMode> {
  public:
   SingleClientPlusAddressSyncTest() : SyncTest(SINGLE_CLIENT) {
-    features_.InitWithFeaturesAndParameters(
-        /*enabled_features=*/{{plus_addresses::features::kPlusAddressesEnabled,
-                               {{plus_addresses::features::
-                                     kEnterprisePlusAddressServerUrl.name,
-                                 "https://not-used.com"}}}},
+    std::vector<base::test::FeatureRefAndParams> enabled_features = {
+        {plus_addresses::features::kPlusAddressesEnabled,
+         {{plus_addresses::features::kEnterprisePlusAddressServerUrl.name,
+           "https://not-used.com"}}}};
+    if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+      enabled_features.push_back(
+          {syncer::kReplaceSyncPromosWithSignInPromos, {}});
+    }
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        enabled_features,
         /*disabled_features=*/{});
   }
 
-  // Sets up the sync client in sync-the-feature or sync-the-transport mode,
-  // depending on `GetParam()`. Returns true if setup succeeded.
-  bool SetupSync(SyncTestAccount account = SyncTestAccount::kDefaultAccount) {
-    const bool should_run_in_transport_mode = GetParam();
-    if (should_run_in_transport_mode) {
-      if (!SetupClients()) {
-        return false;
-      }
-
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-      auto enable_disclaimer_on_primary_account_change_resetter =
-          enterprise_util::DisableAutomaticManagementDisclaimerUntilReset(
-              GetProfile(0));
-#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-      return GetClient(0)->SignInPrimaryAccount(account) &&
-             GetClient(0)->AwaitSyncTransportActive();
-    }
-    return SyncTest::SetupSync(account);
+  // SyncTest overrides.
+  SyncTest::SetupSyncMode GetSetupSyncMode() const override {
+    return GetParam();
   }
 
   PlusAddressService* GetPlusAddressService() {
@@ -141,21 +128,13 @@
   }
 
  private:
-  base::test::ScopedFeatureList features_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-INSTANTIATE_TEST_SUITE_P(
-    ,
-    SingleClientPlusAddressSyncTest,
-#if BUILDFLAG(IS_CHROMEOS)
-    // On ChromeOS, sync-the-feature gets started automatically once a primary
-    // account is signed in and transport mode is not a thing. As such, only run
-    // the tests in sync-the-feature mode.
-    testing::Values(false)
-#else
-    testing::Bool()
-#endif
-);
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientPlusAddressSyncTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
 
 IN_PROC_BROWSER_TEST_P(SingleClientPlusAddressSyncTest, InitialSync) {
   // Start syncing with an existing `plus_profile` on the server.
diff --git a/chrome/browser/sync/test/integration/single_client_sharing_message_sync_test.cc b/chrome/browser/sync/test/integration/single_client_sharing_message_sync_test.cc
index dc7e424a8..94f93aac 100644
--- a/chrome/browser/sync/test/integration/single_client_sharing_message_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_sharing_message_sync_test.cc
@@ -7,6 +7,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/test/mock_callback.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/sharing/sharing_message_bridge_factory.h"
@@ -15,6 +16,7 @@
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "components/sharing_message/features.h"
 #include "components/sharing_message/sharing_message_bridge.h"
+#include "components/sync/base/features.h"
 #include "components/sync/service/sync_token_status.h"
 #include "components/sync/test/fake_server_http_post_provider.h"
 #include "content/public/test/browser_test.h"
@@ -155,9 +157,17 @@
   base::WeakPtrFactory<SharingMessageCallbackChecker> weak_ptr_factory_{this};
 };
 
-class SingleClientSharingMessageSyncTest : public SyncTest {
+class SingleClientSharingMessageSyncTest
+    : public SyncTest,
+      public testing::WithParamInterface<SyncTest::SetupSyncMode> {
  public:
-  SingleClientSharingMessageSyncTest() : SyncTest(SINGLE_CLIENT) {}
+  SingleClientSharingMessageSyncTest() : SyncTest(SINGLE_CLIENT) {
+    if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+      scoped_feature_list_.InitAndEnableFeature(
+          syncer::kReplaceSyncPromosWithSignInPromos);
+    }
+  }
+  ~SingleClientSharingMessageSyncTest() override = default;
 
   bool WaitForSharingMessage(
       std::vector<SharingMessageSpecifics> expected_specifics) {
@@ -165,9 +175,21 @@
                                          std::move(expected_specifics))
         .Wait();
   }
+
+  SyncTest::SetupSyncMode GetSetupSyncMode() const override {
+    return GetParam();
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(SingleClientSharingMessageSyncTest, ShouldSubmit) {
+INSTANTIATE_TEST_SUITE_P(All,
+                         SingleClientSharingMessageSyncTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(SingleClientSharingMessageSyncTest, ShouldSubmit) {
   ASSERT_TRUE(SetupSync());
   SharingMessageCallbackChecker callback_checker(
       GetSyncService(0), sync_pb::SharingMessageCommitError::NONE);
@@ -188,40 +210,7 @@
   EXPECT_TRUE(callback_checker.Wait());
 }
 
-// ChromeOS does not support late signin after profile creation, so the test
-// below does not apply, at least in the current form.
-#if !BUILDFLAG(IS_CHROMEOS)
-IN_PROC_BROWSER_TEST_F(SingleClientSharingMessageSyncTest,
-                       ShouldSubmitInTransportMode) {
-  // We avoid calling SetupSync(), because we don't want to turn on full sync,
-  // only sign in such that the standalone transport starts.
-  ASSERT_TRUE(SetupClients());
-  ASSERT_TRUE(GetClient(0)->SignInPrimaryAccount());
-  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
-  ASSERT_FALSE(GetSyncService(0)->IsSyncFeatureActive())
-      << "Full sync should be disabled";
-  ASSERT_EQ(syncer::SyncService::TransportState::ACTIVE,
-            GetSyncService(0)->GetTransportState());
-  ASSERT_TRUE(
-      GetSyncService(0)->GetActiveDataTypes().Has(syncer::SHARING_MESSAGE));
-
-  SharingMessageCallbackChecker callback_checker(
-      GetSyncService(0), sync_pb::SharingMessageCommitError::NONE);
-
-  SharingMessageBridge* sharing_message_bridge =
-      SharingMessageBridgeFactory::GetForBrowserContext(GetProfile(0));
-  SharingMessageSpecifics specifics;
-  specifics.set_payload("payload");
-  sharing_message_bridge->SendSharingMessage(
-      std::make_unique<SharingMessageSpecifics>(specifics),
-      callback_checker.GetCommitFinishedCallback());
-
-  EXPECT_TRUE(WaitForSharingMessage({specifics}));
-  EXPECT_TRUE(callback_checker.Wait());
-}
-#endif  // !BUILDFLAG(IS_CHROMEOS)
-
-IN_PROC_BROWSER_TEST_F(SingleClientSharingMessageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientSharingMessageSyncTest,
                        ShouldPropagateCommitFailure) {
   ASSERT_TRUE(SetupSync());
   SharingMessageCallbackChecker callback_checker(
@@ -242,7 +231,7 @@
 
 // ChromeOS does not support signing out of a primary account.
 #if !BUILDFLAG(IS_CHROMEOS)
-IN_PROC_BROWSER_TEST_F(SingleClientSharingMessageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientSharingMessageSyncTest,
                        ShouldCleanPendingMessagesUponSignout) {
   ASSERT_TRUE(SetupSync());
   SharingMessageCallbackChecker callback_checker(
@@ -266,7 +255,7 @@
 }
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SingleClientSharingMessageSyncTest,
     ShouldTurnOffSharingMessageDataTypeOnPersistentAuthError) {
   ASSERT_TRUE(SetupSync());
@@ -289,7 +278,7 @@
   EXPECT_TRUE(callback_checker.Wait());
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SingleClientSharingMessageSyncTest,
     ShouldRetrySendingSharingMessageDataTypeOnTransientAuthError) {
   const std::string payload = "payload";
diff --git a/chrome/browser/sync/test/integration/single_client_valuables_sync_test.cc b/chrome/browser/sync/test/integration/single_client_valuables_sync_test.cc
index 68c5624..2fbc62a 100644
--- a/chrome/browser/sync/test/integration/single_client_valuables_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_valuables_sync_test.cc
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <tuple>
 #include <vector>
 
+#include "base/strings/strcat.h"
 #include "base/test/protobuf_matchers.h"
 #include "chrome/browser/autofill/autofill_entity_data_manager_factory.h"
 #include "chrome/browser/autofill/valuables_data_manager_factory.h"
@@ -167,6 +169,22 @@
   }
 
  protected:
+  void EnterSyncPausedStateForPrimaryAccount() {
+    if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+      GetClient(0)->EnterSignInPendingStateForPrimaryAccount();
+    } else {
+      GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
+    }
+  }
+
+  void ExitSyncPausedStateForPrimaryAccount() {
+    if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+      GetClient(0)->ExitSignInPendingStateForPrimaryAccount();
+    } else {
+      GetClient(0)->ExitSyncPausedStateForPrimaryAccount();
+    }
+  }
+
   void WaitForNumberOfLoyaltyCards(size_t expected_count,
                                    ValuablesDataManager* vdm) {
     while (vdm->GetLoyaltyCards().size() != expected_count ||
@@ -177,15 +195,20 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-class SingleClientValuablesSyncTest : public SingleClientValuableSyncTestBase,
-                                      public testing::WithParamInterface<bool> {
+class SingleClientValuablesSyncTest
+    : public SingleClientValuableSyncTestBase,
+      public testing::WithParamInterface<
+          std::tuple<bool, SyncTest::SetupSyncMode>> {
  public:
   SingleClientValuablesSyncTest() {
     std::vector<base::test::FeatureRef> enabled_features = {
         autofill::features::kAutofillEnableLoyaltyCardsFilling,
         syncer::kSyncAutofillLoyaltyCard};
+    if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+      enabled_features.push_back(syncer::kReplaceSyncPromosWithSignInPromos);
+    }
     std::vector<base::test::FeatureRef> disabled_features;
-    if (GetParam()) {
+    if (IsValuablesInProfileDBEnabled()) {
       enabled_features.push_back(syncer::kSyncMoveValuablesToProfileDb);
     } else {
       disabled_features.push_back(syncer::kSyncMoveValuablesToProfileDb);
@@ -194,6 +217,12 @@
   }
 
   ~SingleClientValuablesSyncTest() override = default;
+
+  SyncTest::SetupSyncMode GetSetupSyncMode() const override {
+    return std::get<1>(GetParam());
+  }
+
+  bool IsValuablesInProfileDBEnabled() const { return std::get<0>(GetParam()); }
 };
 
 // Valuables data should get loaded on initial sync.
@@ -240,12 +269,12 @@
   EXPECT_THAT(vdm->GetLoyaltyCards(), ElementsAre(loyalty_card));
 
   // Enter sync paused state, the data & metadata should be gone.
-  GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
+  EnterSyncPausedStateForPrimaryAccount();
   WaitForNumberOfLoyaltyCards(0, vdm);
   EXPECT_EQ(0uL, vdm->GetLoyaltyCards().size());
 
   // When exiting the sync paused state, the data should be redownloaded.
-  GetClient(0)->ExitSyncPausedStateForPrimaryAccount();
+  ExitSyncPausedStateForPrimaryAccount();
   WaitForNumberOfLoyaltyCards(1, vdm);
   EXPECT_EQ(1uL, vdm->GetLoyaltyCards().size());
 }
@@ -307,21 +336,28 @@
 INSTANTIATE_TEST_SUITE_P(
     ,
     SingleClientValuablesSyncTest,
-    testing::Bool(),
+    testing::Combine(testing::Bool(), GetSyncTestModes()),
     [](const testing::TestParamInfo<SingleClientValuablesSyncTest::ParamType>&
            info) {
-      return info.param ? "ValuablesInProfileDB" : "ValuablesInAccountDB";
+      return base::StrCat({std::get<0>(info.param) ? "ValuablesInProfileDB"
+                                                   : "ValuablesInAccountDB",
+                           testing::PrintToString(std::get<1>(info.param))});
     });
 
 // DB migration tests for valuables.
 class MigrateValuableDatabasesSyncTest
-    : public SingleClientValuableSyncTestBase {
+    : public SingleClientValuableSyncTestBase,
+      public testing::WithParamInterface<SyncTest::SetupSyncMode> {
  public:
   MigrateValuableDatabasesSyncTest() {
     std::vector<base::test::FeatureRef> enabled_features = {
         autofill::features::kAutofillEnableLoyaltyCardsFilling,
         syncer::kSyncAutofillLoyaltyCard};
 
+    if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+      enabled_features.push_back(syncer::kReplaceSyncPromosWithSignInPromos);
+    }
+
     std::vector<base::test::FeatureRef> disabled_features;
     if (GetTestPreCount() == 0 || GetTestPreCount() == 2) {
       disabled_features.push_back(syncer::kSyncMoveValuablesToProfileDb);
@@ -339,6 +375,10 @@
   }
   ~MigrateValuableDatabasesSyncTest() override = default;
 
+  SyncTest::SetupSyncMode GetSetupSyncMode() const override {
+    return GetParam();
+  }
+
  protected:
   const LoyaltyCard loyalty_card1_ = CreateLoyaltyCard();
   const LoyaltyCard loyalty_card2_ = CreateLoyaltyCard2();
@@ -346,7 +386,7 @@
 
 // With `kSyncMoveValuablesToProfileDb` disabled, valuables are loaded normally
 // from the account DB.
-IN_PROC_BROWSER_TEST_F(MigrateValuableDatabasesSyncTest,
+IN_PROC_BROWSER_TEST_P(MigrateValuableDatabasesSyncTest,
                        PRE_PRE_MigrateValuablesDB) {
   ASSERT_TRUE(SetupClients());
   ASSERT_TRUE(SetupSync());
@@ -361,7 +401,7 @@
 // With `kSyncMoveValuablesToProfileDb` enabled, valuables storage is migrated
 // to the profile DB. The DB starts fresh and sync downloads the latest set of
 // valuables.
-IN_PROC_BROWSER_TEST_F(MigrateValuableDatabasesSyncTest,
+IN_PROC_BROWSER_TEST_P(MigrateValuableDatabasesSyncTest,
                        PRE_MigrateValuablesDB) {
   ASSERT_TRUE(SetupClients());
   ValuablesDataManager* vdm = GetValuablesDataManager(0);
@@ -374,7 +414,7 @@
 
 // With `kSyncMoveValuablesToProfileDb` disabled again, valuables are loaded
 // from the account DB again.
-IN_PROC_BROWSER_TEST_F(MigrateValuableDatabasesSyncTest, MigrateValuablesDB) {
+IN_PROC_BROWSER_TEST_P(MigrateValuableDatabasesSyncTest, MigrateValuablesDB) {
   ASSERT_TRUE(SetupClients());
   ValuablesDataManager* vdm = GetValuablesDataManager(0);
   ASSERT_NE(nullptr, vdm);
@@ -384,18 +424,31 @@
               UnorderedElementsAre(loyalty_card1_, loyalty_card2_));
 }
 
+INSTANTIATE_TEST_SUITE_P(,
+                         MigrateValuableDatabasesSyncTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
 class SingleClientEntityValuablesSyncTest
-    : public SingleClientValuableSyncTestBase {
+    : public SingleClientValuableSyncTestBase,
+      public testing::WithParamInterface<SyncTest::SetupSyncMode> {
  public:
   SingleClientEntityValuablesSyncTest() {
     std::vector<base::test::FeatureRef> enabled_features = {
         syncer::kSyncAutofillLoyaltyCard, syncer::kSyncMoveValuablesToProfileDb,
         syncer::kSyncWalletFlightReservations,
         syncer::kSyncWalletVehicleRegistrations};
+    if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
+      enabled_features.push_back(syncer::kReplaceSyncPromosWithSignInPromos);
+    }
     feature_list_.InitWithFeatures(enabled_features, {});
   }
 
   ~SingleClientEntityValuablesSyncTest() override = default;
+
+  SyncTest::SetupSyncMode GetSetupSyncMode() const override {
+    return GetParam();
+  }
   EntityDataManager* GetEntityDataManager(int index) {
     return AutofillEntityDataManagerFactory::GetForProfile(
         test()->GetProfile(index));
@@ -412,7 +465,7 @@
 };
 
 // Entities data should get loaded on initial sync.
-IN_PROC_BROWSER_TEST_F(SingleClientEntityValuablesSyncTest, InitialSync) {
+IN_PROC_BROWSER_TEST_P(SingleClientEntityValuablesSyncTest, InitialSync) {
   const EntityInstance vehicle = GetServerVehicleEntityInstanceWithRandomGuid();
   const EntityInstance flight =
       GetFlightReservationEntityInstanceWithRandomGuid();
@@ -430,7 +483,7 @@
 #if !BUILDFLAG(IS_CHROMEOS)
 // Wallet entities should get cleared from the entity database when the user
 // signs out.
-IN_PROC_BROWSER_TEST_F(SingleClientEntityValuablesSyncTest, ClearOnSignOut) {
+IN_PROC_BROWSER_TEST_P(SingleClientEntityValuablesSyncTest, ClearOnSignOut) {
   const EntityInstance vehicle = GetServerVehicleEntityInstanceWithRandomGuid();
   const EntityInstance flight =
       GetFlightReservationEntityInstanceWithRandomGuid();
@@ -451,7 +504,7 @@
 
 // Wallet should get cleared from the database when the user enters the
 // sync paused state (e.g. persistent auth error).
-IN_PROC_BROWSER_TEST_F(SingleClientEntityValuablesSyncTest, ClearOnSyncPaused) {
+IN_PROC_BROWSER_TEST_P(SingleClientEntityValuablesSyncTest, ClearOnSyncPaused) {
   const EntityInstance vehicle = GetServerVehicleEntityInstanceWithRandomGuid();
   const EntityInstance flight =
       GetFlightReservationEntityInstanceWithRandomGuid();
@@ -464,19 +517,19 @@
   EXPECT_THAT(edm->GetEntityInstances(), UnorderedElementsAre(vehicle, flight));
 
   // Enter sync paused state, the data & metadata should be gone.
-  GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
+  EnterSyncPausedStateForPrimaryAccount();
   WaitForNumberOfEntityInstancesCards(0, edm);
   EXPECT_EQ(0uL, edm->GetEntityInstances().size());
 
   // When exiting the sync paused state, the data should be redownloaded.
-  GetClient(0)->ExitSyncPausedStateForPrimaryAccount();
+  ExitSyncPausedStateForPrimaryAccount();
   WaitForNumberOfEntityInstancesCards(2, edm);
   EXPECT_EQ(2uL, edm->GetEntityInstances().size());
 }
 
 // Valuables are not using incremental updates. Make sure existing entities gets
 // replaced when synced down.
-IN_PROC_BROWSER_TEST_F(SingleClientEntityValuablesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientEntityValuablesSyncTest,
                        NewSyncDataShouldReplaceExistingData) {
   const EntityInstance vehicle = GetServerVehicleEntityInstanceWithRandomGuid();
   const EntityInstance flight =
@@ -502,7 +555,7 @@
 
 // Wallet entities should get cleared from the entity database when the user
 // disables payments sync.
-IN_PROC_BROWSER_TEST_F(SingleClientEntityValuablesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientEntityValuablesSyncTest,
                        ClearOnDisablePaymentsSync) {
   const EntityInstance vehicle = GetServerVehicleEntityInstanceWithRandomGuid();
   const EntityInstance flight =
@@ -524,7 +577,7 @@
 }
 
 // Verifies that local entities are never uploaded to the sync server.
-IN_PROC_BROWSER_TEST_F(SingleClientEntityValuablesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientEntityValuablesSyncTest,
                        NotUploadLocalEntity) {
   ASSERT_TRUE(SetupSync());
   EntityDataManager* edm = GetEntityDataManager(0);
@@ -538,7 +591,7 @@
 
 // Verifies that a new wallet entity created locally is successfully uploaded to
 // the sync server.
-IN_PROC_BROWSER_TEST_F(SingleClientEntityValuablesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientEntityValuablesSyncTest,
                        UploadWalletEntity) {
   ASSERT_TRUE(SetupSync());
   EntityDataManager* edm = GetEntityDataManager(0);
@@ -564,7 +617,7 @@
 
 // Verifies that updating an existing wallet entity locally correctly propagates
 // that update to the sync server.
-IN_PROC_BROWSER_TEST_F(SingleClientEntityValuablesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientEntityValuablesSyncTest,
                        UploadAndUpdateWalletEntity) {
   ASSERT_TRUE(SetupSync());
   EntityDataManager* edm = GetEntityDataManager(0);
@@ -584,7 +637,7 @@
 
 // Verifies that simultaneous local and remote changes are applied consistently.
 // In this case, both updates are complementary. No common entity is affected.
-IN_PROC_BROWSER_TEST_F(SingleClientEntityValuablesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientEntityValuablesSyncTest,
                        SimultaneousLocalAndRemoteChangeNoCommonEntity) {
   const EntityInstance initial_vehicle =
       GetServerVehicleEntityInstanceWithRandomGuid();
@@ -626,7 +679,7 @@
 // Verifies that simultaneous local and remote changes are applied consistently.
 // In this case, a local update is applied even if the same entity is received
 // via a server update.
-IN_PROC_BROWSER_TEST_F(SingleClientEntityValuablesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientEntityValuablesSyncTest,
                        SimultaneousLocalAndRemoteChangeCommonEntityNoConflict) {
   const EntityInstance server_vehicle = GetServerVehicleEntityInstance();
   GetFakeServer()->SetValuableData(
@@ -662,7 +715,7 @@
 // Verifies that simultaneous local and remote changes are applied consistently.
 // In this case, conflicting updated versions of the same entity are received
 // from the server and updated locally. The server entity must prevail.
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SingleClientEntityValuablesSyncTest,
     SimultaneousLocalAndRemoteChangeCommonEntityWithConflict) {
   const EntityInstance server_vehicle = GetServerVehicleEntityInstance();
@@ -697,7 +750,7 @@
 }
 
 // Verifies that server updates override client entities.
-IN_PROC_BROWSER_TEST_F(SingleClientEntityValuablesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientEntityValuablesSyncTest,
                        ServerOverridesClientChanges) {
   ASSERT_TRUE(SetupSync());
   EntityDataManager* edm = GetEntityDataManager(0);
@@ -727,4 +780,9 @@
               UnorderedElementsAre(server_vehicle, server_flight));
 }
 
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientEntityValuablesSyncTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
 }  // namespace
diff --git a/chrome/browser/sync/test/integration/wallet_helper.cc b/chrome/browser/sync/test/integration/wallet_helper.cc
index 8addb3a..0b6669c8 100644
--- a/chrome/browser/sync/test/integration/wallet_helper.cc
+++ b/chrome/browser/sync/test/integration/wallet_helper.cc
@@ -336,12 +336,19 @@
   return cards_metadata;
 }
 
-sync_pb::DataTypeState GetWalletDataTypeState(syncer::DataType data_type,
-                                              int profile) {
+sync_pb::DataTypeState GetWalletDataTypeState(
+    syncer::DataType data_type,
+    int profile,
+    SyncTest::SetupSyncMode setup_sync_mode) {
   DCHECK(data_type == syncer::AUTOFILL_WALLET_DATA ||
          data_type == syncer::AUTOFILL_WALLET_OFFER);
   sync_pb::DataTypeState result;
-  scoped_refptr<AutofillWebDataService> wds = GetProfileWebDataService(profile);
+  scoped_refptr<AutofillWebDataService> wds;
+  if (setup_sync_mode == SyncTest::SetupSyncMode::kSyncTransportOnly) {
+    wds = GetAccountWebDataService(profile);
+  } else {
+    wds = GetProfileWebDataService(profile);
+  }
   wds->GetDBTaskRunner()->PostTask(
       FROM_HERE, base::BindOnce(&GetDataTypeStateOnDBSequence, data_type,
                                 base::Unretained(wds.get()), &result));
diff --git a/chrome/browser/sync/test/integration/wallet_helper.h b/chrome/browser/sync/test/integration/wallet_helper.h
index ab736b7a..59603d9 100644
--- a/chrome/browser/sync/test/integration/wallet_helper.h
+++ b/chrome/browser/sync/test/integration/wallet_helper.h
@@ -15,6 +15,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "chrome/browser/sync/test/integration/multi_client_status_change_checker.h"
+#include "chrome/browser/sync/test/integration/sync_test.h"
 #include "components/autofill/core/browser/data_manager/payments/payments_data_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -86,8 +87,13 @@
 std::vector<autofill::PaymentsMetadata> GetServerCardsMetadata(int profile);
 
 // Function supports AUTOFILL_WALLET_DATA and AUTOFILL_WALLET_OFFER.
-sync_pb::DataTypeState GetWalletDataTypeState(syncer::DataType type,
-                                              int profile);
+// TODO(crbug.com/353425612): The default value for setup_sync_mode should be
+// removed once all callers are updated.
+sync_pb::DataTypeState GetWalletDataTypeState(
+    syncer::DataType type,
+    int profile,
+    SyncTest::SetupSyncMode setup_sync_mode =
+        SyncTest::SetupSyncMode::kSyncTheFeature);
 
 sync_pb::SyncEntity CreateDefaultSyncWalletCard();
 
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/StorageLoadedData.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/StorageLoadedData.java
index a33edb2..b268019 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/StorageLoadedData.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/StorageLoadedData.java
@@ -9,7 +9,6 @@
 import org.jni_zero.JniType;
 import org.jni_zero.NativeMethods;
 
-import org.chromium.base.JniOnceCallback;
 import org.chromium.base.Token;
 import org.chromium.base.lifetime.Destroyable;
 import org.chromium.build.annotations.NullMarked;
@@ -29,16 +28,9 @@
         public final @TabId int tabId;
         public final TabState tabState;
 
-        /** This must always be run or destroyed to avoid leaks. */
-        public final JniOnceCallback<@Nullable Tab> onTabCreationCallback;
-
-        public LoadedTabState(
-                @TabId int tabId,
-                TabState tabState,
-                JniOnceCallback<@Nullable Tab> onTabCreationCallback) {
+        public LoadedTabState(@TabId int tabId, TabState tabState) {
             this.tabId = tabId;
             this.tabState = tabState;
-            this.onTabCreationCallback = onTabCreationCallback;
         }
     }
 
@@ -60,11 +52,6 @@
 
     @Override
     public void destroy() {
-        for (LoadedTabState loadedTabState : getLoadedTabStates()) {
-            loadedTabState.onTabCreationCallback.destroy();
-            // We don't destroy the contentsState here. Leave this to clients to clean.
-        }
-
         for (TabGroupCollectionData groupData : getGroupsData()) {
             groupData.destroy();
         }
@@ -89,11 +76,8 @@
     }
 
     @CalledByNative
-    public static LoadedTabState createLoadedTabState(
-            @TabId int tabId,
-            TabState tabState,
-            JniOnceCallback<@Nullable Tab> onTabCreationCallback) {
-        return new LoadedTabState(tabId, tabState, onTabCreationCallback);
+    public static LoadedTabState createLoadedTabState(@TabId int tabId, TabState tabState) {
+        return new LoadedTabState(tabId, tabState);
     }
 
     @CalledByNative
diff --git a/chrome/browser/tab/storage_loaded_data.cc b/chrome/browser/tab/storage_loaded_data.cc
index 5babd591..1727422 100644
--- a/chrome/browser/tab/storage_loaded_data.cc
+++ b/chrome/browser/tab/storage_loaded_data.cc
@@ -7,7 +7,7 @@
 namespace tabs {
 
 StorageLoadedData::StorageLoadedData(
-    std::vector<LoadedTabState> loaded_tabs,
+    std::vector<tabs_pb::TabState> loaded_tabs,
     std::vector<std::unique_ptr<TabGroupCollectionData>> loaded_groups,
     std::unique_ptr<RestoreIdAssociator> node_associator,
     std::optional<int> active_tab_index)
@@ -24,7 +24,7 @@
   return node_associator_.get();
 }
 
-std::vector<LoadedTabState>& StorageLoadedData::GetLoadedTabs() {
+std::vector<tabs_pb::TabState>& StorageLoadedData::GetLoadedTabs() {
   return loaded_tabs_;
 }
 
diff --git a/chrome/browser/tab/storage_loaded_data.h b/chrome/browser/tab/storage_loaded_data.h
index e3c3fc1..432aff3 100644
--- a/chrome/browser/tab/storage_loaded_data.h
+++ b/chrome/browser/tab/storage_loaded_data.h
@@ -21,14 +21,11 @@
 
 namespace tabs {
 
-using OnTabInterfaceCreation = base::OnceCallback<void(const TabInterface*)>;
-using LoadedTabState = std::pair<tabs_pb::TabState, OnTabInterfaceCreation>;
-
 // Represents data loaded from the database.
 class StorageLoadedData {
  public:
   StorageLoadedData(
-      std::vector<LoadedTabState> loaded_tabs,
+      std::vector<tabs_pb::TabState> loaded_tabs,
       std::vector<std::unique_ptr<TabGroupCollectionData>> loaded_groups,
       std::unique_ptr<RestoreIdAssociator> associator,
       std::optional<int> active_tab_index);
@@ -41,12 +38,12 @@
   StorageLoadedData& operator=(StorageLoadedData&&);
 
   RestoreIdAssociator* GetNodeAssociator() const;
-  std::vector<LoadedTabState>& GetLoadedTabs();
+  std::vector<tabs_pb::TabState>& GetLoadedTabs();
   std::vector<std::unique_ptr<TabGroupCollectionData>>& GetLoadedGroups();
   std::optional<int> GetActiveTabIndex() const;
 
  private:
-  std::vector<LoadedTabState> loaded_tabs_;
+  std::vector<tabs_pb::TabState> loaded_tabs_;
   std::vector<std::unique_ptr<TabGroupCollectionData>> loaded_groups_;
   std::unique_ptr<RestoreIdAssociator> node_associator_;
   std::optional<int> active_tab_index_;
diff --git a/chrome/browser/tab/tab_state_storage_service.cc b/chrome/browser/tab/tab_state_storage_service.cc
index eb1d6c71..78d798f5 100644
--- a/chrome/browser/tab/tab_state_storage_service.cc
+++ b/chrome/browser/tab/tab_state_storage_service.cc
@@ -92,8 +92,8 @@
     StorageId current_node_storage_id,
     std::optional<StorageId> active_tab_storage_id,
     const absl::flat_hash_map<StorageId, std::vector<StorageId>>& children_map,
-    absl::flat_hash_map<StorageId, LoadedTabState>& loaded_tabs_map,
-    std::vector<LoadedTabState>& sorted_tabs,
+    absl::flat_hash_map<StorageId, tabs_pb::TabState>& loaded_tabs_map,
+    std::vector<tabs_pb::TabState>& sorted_tabs,
     std::optional<int>& active_tab_index,
     int depth = 0) {
   DCHECK_LE(depth, kMaxTreeHeight) << "Tree is too tall, possible cycle?";
@@ -248,7 +248,7 @@
 
   if (entries.empty()) {
     std::move(callback).Run(std::make_unique<StorageLoadedData>(
-        std::vector<LoadedTabState>(),
+        std::vector<tabs_pb::TabState>(),
         std::vector<std::unique_ptr<TabGroupCollectionData>>(),
         builder->BuildAssociator(), std::nullopt));
     return;
@@ -256,7 +256,7 @@
 
   std::optional<StorageId> root_storage_id;
   std::optional<StorageId> active_tab_storage_id;
-  absl::flat_hash_map<StorageId, LoadedTabState> loaded_tabs_map;
+  absl::flat_hash_map<StorageId, tabs_pb::TabState> loaded_tabs_map;
   absl::flat_hash_map<StorageId, std::vector<StorageId>> children_map;
   std::vector<std::unique_ptr<TabGroupCollectionData>> loaded_groups;
 
@@ -266,12 +266,7 @@
       if (tab_state.ParseFromArray(entry.payload.data(),
                                    entry.payload.size())) {
         builder->RegisterTab(entry.id, tab_state);
-        loaded_tabs_map.emplace(
-            entry.id,
-            std::make_pair(
-                std::move(tab_state),
-                base::BindOnce(&TabStateStorageService::OnTabCreated,
-                               weak_ptr_factory_.GetWeakPtr(), entry.id)));
+        loaded_tabs_map.emplace(entry.id, std::move(tab_state));
       }
     } else {
       if (entry.type == TabStorageType::kTabStrip) {
@@ -310,7 +305,7 @@
     }
   }
 
-  std::vector<LoadedTabState> loaded_tabs;
+  std::vector<tabs_pb::TabState> loaded_tabs;
   loaded_tabs.reserve(loaded_tabs_map.size());
   std::optional<int> active_tab_index;
 
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java
index dcf53bc..4686222 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java
@@ -21,6 +21,7 @@
 import static org.chromium.chrome.browser.autofill.AutofillTestHelper.createCreditCard;
 import static org.chromium.chrome.browser.autofill.AutofillTestHelper.createCreditCardSuggestion;
 import static org.chromium.chrome.browser.autofill.AutofillTestHelper.createVirtualCreditCard;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.AFFIRM_TOS_SCREEN;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.ERROR_SCREEN_DISMISSED;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.ERROR_SCREEN_SHOWN;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.ISSUER_SELECTION_SCREEN_AFFIRM_LINKED_SELECTED;
@@ -33,8 +34,13 @@
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.ISSUER_SELECTION_SCREEN_SHOWN;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.ISSUER_SELECTION_SCREEN_ZIP_LINKED_SELECTED;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.ISSUER_SELECTION_SCREEN_ZIP_UNLINKED_SELECTED;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.KLARNA_TOS_SCREEN;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.LEGAL_MESSAGE_LINK_CLICKED;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.PROGRESS_SCREEN_DISMISSED;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.PROGRESS_SCREEN_SHOWN;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.SCREEN_ACCEPTED;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.SCREEN_DISMISSED;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.SCREEN_SHOWN;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.TOUCH_TO_FILL_AFFILIATED_LOYALTY_CARDS_SCREEN_INDEX_SELECTED;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.TOUCH_TO_FILL_ALL_LOYALTY_CARDS_SCREEN_INDEX_SELECTED;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.TOUCH_TO_FILL_BNPL_SELECT_ISSUER_NUMBER_OF_ISSUERS_SHOWN;
@@ -48,6 +54,8 @@
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.TOUCH_TO_FILL_NUMBER_OF_CARDS_SHOWN;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.TOUCH_TO_FILL_NUMBER_OF_IBANS_SHOWN;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.TOUCH_TO_FILL_NUMBER_OF_LOYALTY_CARDS_SHOWN;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.WALLET_LINK_CLICKED;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.ZIP_TOS_SCREEN;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.BACK_PRESS_HANDLER;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.BnplIssuerContextProperties.APPLY_ISSUER_DEACTIVATED_STYLE;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.BnplIssuerContextProperties.ISSUER_ICON_ID;
@@ -103,6 +111,7 @@
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.IBAN;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.LOYALTY_CARD;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.TERMS_LABEL;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.TEXT_BUTTON;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.TOS_FOOTER;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.WALLET_SETTINGS_BUTTON;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.LoyaltyCardProperties.LOYALTY_CARD_NUMBER;
@@ -121,10 +130,13 @@
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ScreenId.PROGRESS_SCREEN;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.TermsLabelProperties.TERMS_LABEL_TEXT_ID;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.TosFooterProperties.LEGAL_MESSAGE_LINES;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.TosFooterProperties.LINK_OPENER;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.VISIBLE;
 
 import android.app.Activity;
+import android.text.SpannableString;
 import android.text.TextUtils;
+import android.text.style.ClickableSpan;
 
 import androidx.annotation.IdRes;
 import androidx.annotation.StringRes;
@@ -173,6 +185,7 @@
 import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.widget.TextViewWithClickableSpans;
 import org.chromium.url.GURL;
 
 import java.util.Arrays;
@@ -470,7 +483,7 @@
                     /* isLinked= */ false,
                     /* isEligible= */ false);
     private static final String LEGAL_MESSAGE_LINE = "legal message line";
-    private static final BnplIssuerTosDetail BNPL_ISSUER_TOS_DETAIL =
+    private static final BnplIssuerTosDetail BNPL_ISSUER_TOS_DETAIL_AFFIRM =
             new BnplIssuerTosDetail(
                     /* issuerId= */ "affirm",
                     /* headerIconDrawableId= */ R.drawable.bnpl_icon_generic,
@@ -479,6 +492,33 @@
                     /* issuerName= */ "Affirm",
                     /* legalMessageLines= */ Arrays.asList(
                             new LegalMessageLine(LEGAL_MESSAGE_LINE)));
+    private static final BnplIssuerTosDetail BNPL_ISSUER_TOS_DETAIL_ZIP =
+            new BnplIssuerTosDetail(
+                    /* issuerId= */ "zip",
+                    /* headerIconDrawableId= */ R.drawable.bnpl_icon_generic,
+                    /* headerIconDarkDrawableId= */ R.drawable.bnpl_icon_generic,
+                    /* isLinkedIssuer= */ false,
+                    /* issuerName= */ "Zip",
+                    /* legalMessageLines= */ Arrays.asList(
+                            new LegalMessageLine(LEGAL_MESSAGE_LINE)));
+    private static final BnplIssuerTosDetail BNPL_ISSUER_TOS_DETAIL_KLARNA =
+            new BnplIssuerTosDetail(
+                    /* issuerId= */ "klarna",
+                    /* headerIconDrawableId= */ R.drawable.bnpl_icon_generic,
+                    /* headerIconDarkDrawableId= */ R.drawable.bnpl_icon_generic,
+                    /* isLinkedIssuer= */ false,
+                    /* issuerName= */ "Klarna",
+                    /* legalMessageLines= */ Arrays.asList(
+                            new LegalMessageLine(LEGAL_MESSAGE_LINE)));
+    private static final BnplIssuerTosDetail BNPL_ISSUER_TOS_DETAIL_UNKNOWN =
+            new BnplIssuerTosDetail(
+                    /* issuerId= */ "test",
+                    /* headerIconDrawableId= */ R.drawable.bnpl_icon_generic,
+                    /* headerIconDarkDrawableId= */ R.drawable.bnpl_icon_generic,
+                    /* isLinkedIssuer= */ false,
+                    /* issuerName= */ "Test",
+                    /* legalMessageLines= */ Arrays.asList(
+                            new LegalMessageLine(LEGAL_MESSAGE_LINE)));
     private static final BnplIssuerContext UNKNOWN_BNPL_ISSUER_CONTEXT =
             new BnplIssuerContext(
                     /* iconId= */ R.drawable.bnpl_icon_generic,
@@ -1665,7 +1705,7 @@
 
     @Test
     public void testShowBnplIssuerTos() throws TimeoutException {
-        mCoordinator.showBnplIssuerTos(BNPL_ISSUER_TOS_DETAIL);
+        mCoordinator.showBnplIssuerTos(BNPL_ISSUER_TOS_DETAIL_AFFIRM);
 
         assertThat(mTouchToFillPaymentMethodModel.get(CURRENT_SCREEN), is(BNPL_ISSUER_TOS_SCREEN));
         ModelList itemList = mTouchToFillPaymentMethodModel.get(SHEET_ITEMS);
@@ -1686,7 +1726,7 @@
                 is(
                         mActivity.getString(
                                 R.string.autofill_bnpl_tos_unlinked_title,
-                                BNPL_ISSUER_TOS_DETAIL.getIssuerName())));
+                                BNPL_ISSUER_TOS_DETAIL_AFFIRM.getIssuerName())));
         assertThat(headerModel.get(0).get(IMAGE_DRAWABLE_ID), is(R.drawable.bnpl_icon_generic));
 
         List<PropertyModel> bnplTosItemModel = getModelsOfType(itemList, BNPL_TOS_TEXT);
@@ -1696,21 +1736,22 @@
                 is(
                         mActivity.getString(
                                 R.string.autofill_bnpl_tos_review_text,
-                                BNPL_ISSUER_TOS_DETAIL.getIssuerName())));
+                                BNPL_ISSUER_TOS_DETAIL_AFFIRM.getIssuerName())));
         assertThat(bnplTosItemModel.get(0).get(BNPL_TOS_ICON_ID), is(R.drawable.checklist));
         assertThat(
                 bnplTosItemModel.get(1).get(DESCRIPTION_TEXT),
                 is(
                         mActivity.getString(
                                 R.string.autofill_bnpl_tos_approve_text,
-                                BNPL_ISSUER_TOS_DETAIL.getIssuerName())));
+                                BNPL_ISSUER_TOS_DETAIL_AFFIRM.getIssuerName())));
         assertThat(bnplTosItemModel.get(1).get(BNPL_TOS_ICON_ID), is(R.drawable.receipt_long));
         assertThat(
                 bnplTosItemModel.get(2).get(DESCRIPTION_TEXT).toString(),
                 is(
                         mCoordinator
                                 .getMediatorForTesting()
-                                .getLinkTextForBnplTosScreen(BNPL_ISSUER_TOS_DETAIL.getIssuerName())
+                                .getLinkTextForBnplTosScreen(
+                                        BNPL_ISSUER_TOS_DETAIL_AFFIRM.getIssuerName())
                                 .toString()));
         assertThat(bnplTosItemModel.get(2).get(BNPL_TOS_ICON_ID), is(R.drawable.add_link));
 
@@ -1722,8 +1763,49 @@
     }
 
     @Test
+    public void testAffirmBnplTosShownLogged() throws TimeoutException {
+        mCoordinator.showBnplIssuerTos(BNPL_ISSUER_TOS_DETAIL_AFFIRM);
+
+        assertThat(mTouchToFillPaymentMethodModel.get(CURRENT_SCREEN), is(BNPL_ISSUER_TOS_SCREEN));
+        assertEquals(
+                1,
+                mActionTester.getActionCount(
+                        TOUCH_TO_FILL_BNPL_USER_ACTION + AFFIRM_TOS_SCREEN + SCREEN_SHOWN));
+    }
+
+    @Test
+    public void testKlarnaBnplTosShownLogged() throws TimeoutException {
+        mCoordinator.showBnplIssuerTos(BNPL_ISSUER_TOS_DETAIL_KLARNA);
+
+        assertThat(mTouchToFillPaymentMethodModel.get(CURRENT_SCREEN), is(BNPL_ISSUER_TOS_SCREEN));
+        assertEquals(
+                1,
+                mActionTester.getActionCount(
+                        TOUCH_TO_FILL_BNPL_USER_ACTION + KLARNA_TOS_SCREEN + SCREEN_SHOWN));
+    }
+
+    @Test
+    public void testZipBnplTosShownLogged() throws TimeoutException {
+        mCoordinator.showBnplIssuerTos(BNPL_ISSUER_TOS_DETAIL_ZIP);
+
+        assertThat(mTouchToFillPaymentMethodModel.get(CURRENT_SCREEN), is(BNPL_ISSUER_TOS_SCREEN));
+        assertEquals(
+                1,
+                mActionTester.getActionCount(
+                        TOUCH_TO_FILL_BNPL_USER_ACTION + ZIP_TOS_SCREEN + SCREEN_SHOWN));
+    }
+
+    @Test
+    public void testUnknownBnplIssuerTosShownNotLogged() throws TimeoutException {
+        mCoordinator.showBnplIssuerTos(BNPL_ISSUER_TOS_DETAIL_UNKNOWN);
+
+        assertThat(mTouchToFillPaymentMethodModel.get(CURRENT_SCREEN), is(BNPL_ISSUER_TOS_SCREEN));
+        assertFalse(mActionTester.getActions().contains(TOUCH_TO_FILL_BNPL_USER_ACTION));
+    }
+
+    @Test
     public void testProgressScreenShownAfterBnplTosAcceptance() throws TimeoutException {
-        mCoordinator.showBnplIssuerTos(BNPL_ISSUER_TOS_DETAIL);
+        mCoordinator.showBnplIssuerTos(BNPL_ISSUER_TOS_DETAIL_AFFIRM);
         assertThat(mTouchToFillPaymentMethodModel.get(CURRENT_SCREEN), is(BNPL_ISSUER_TOS_SCREEN));
 
         mClock.advanceCurrentTimeMillis(InputProtector.POTENTIALLY_UNINTENDED_INPUT_THRESHOLD);
@@ -1737,6 +1819,75 @@
     }
 
     @Test
+    public void testBnplTosScreenAcceptedHistogram() {
+        mCoordinator.showBnplIssuerTos(BNPL_ISSUER_TOS_DETAIL_AFFIRM);
+        mClock.advanceCurrentTimeMillis(InputProtector.POTENTIALLY_UNINTENDED_INPUT_THRESHOLD);
+        getModelsOfType(mTouchToFillPaymentMethodModel.get(SHEET_ITEMS), FILL_BUTTON)
+                .get(0)
+                .get(ON_CLICK_ACTION)
+                .run();
+
+        verify(mDelegateMock).onBnplTosAccepted();
+        assertEquals(
+                1,
+                mActionTester.getActionCount(
+                        TOUCH_TO_FILL_BNPL_USER_ACTION + AFFIRM_TOS_SCREEN + SCREEN_ACCEPTED));
+    }
+
+    @Test
+    public void testBnplTosScreenDismissedHistogram() {
+        mCoordinator.showPaymentMethods(
+                List.of(VISA_SUGGESTION, MASTERCARD_SUGGESTION),
+                /* shouldShowScanCreditCard= */ true);
+        mCoordinator.showBnplIssuers(List.of(BNPL_ISSUER_CONTEXT_AFFIRM_LINKED));
+        mCoordinator.showBnplIssuerTos(BNPL_ISSUER_TOS_DETAIL_ZIP);
+        ModelList itemList = mTouchToFillPaymentMethodModel.get(SHEET_ITEMS);
+        getModelsOfType(mTouchToFillPaymentMethodModel.get(SHEET_ITEMS), TEXT_BUTTON)
+                .get(0)
+                .get(ON_CLICK_ACTION)
+                .run();
+
+        assertEquals(
+                1,
+                mActionTester.getActionCount(
+                        TOUCH_TO_FILL_BNPL_USER_ACTION + ZIP_TOS_SCREEN + SCREEN_DISMISSED));
+    }
+
+    @Test
+    public void testBnplTosScreenWalletLinkClickedHistogram() {
+        mCoordinator.showBnplIssuerTos(BNPL_ISSUER_TOS_DETAIL_KLARNA);
+        mClock.advanceCurrentTimeMillis(InputProtector.POTENTIALLY_UNINTENDED_INPUT_THRESHOLD);
+        List<PropertyModel> bnplTosTextModel =
+                getModelsOfType(mTouchToFillPaymentMethodModel.get(SHEET_ITEMS), BNPL_TOS_TEXT);
+        assertThat(bnplTosTextModel.size(), is(3));
+        SpannableString linkText = (SpannableString) bnplTosTextModel.get(2).get(DESCRIPTION_TEXT);
+        linkText.getSpans(0, linkText.length(), ClickableSpan.class)[0].onClick(
+                new TextViewWithClickableSpans(mActivity));
+
+        assertEquals(
+                1,
+                mActionTester.getActionCount(
+                        TOUCH_TO_FILL_BNPL_USER_ACTION + KLARNA_TOS_SCREEN + WALLET_LINK_CLICKED));
+    }
+
+    @Test
+    public void testBnplTosScreenLegalMessageLinkClickedHistogram() {
+        mCoordinator.showBnplIssuerTos(BNPL_ISSUER_TOS_DETAIL_AFFIRM);
+        mClock.advanceCurrentTimeMillis(InputProtector.POTENTIALLY_UNINTENDED_INPUT_THRESHOLD);
+        getModelsOfType(mTouchToFillPaymentMethodModel.get(SHEET_ITEMS), TOS_FOOTER)
+                .get(0)
+                .get(LINK_OPENER)
+                .accept("http://www.test.com");
+
+        assertEquals(
+                1,
+                mActionTester.getActionCount(
+                        TOUCH_TO_FILL_BNPL_USER_ACTION
+                                + AFFIRM_TOS_SCREEN
+                                + LEGAL_MESSAGE_LINK_CLICKED));
+    }
+
+    @Test
     public void testShowErrorScreen() {
         mCoordinator
                 .getMediatorForTesting()
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java
index 03a3f18..b8f7f2c 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java
@@ -215,6 +215,24 @@
         int MAX_VALUE = DISMISS;
     }
 
+    /** The user actions on the BNPL ToS Touch To Fill sheet. */
+    @IntDef({
+        TouchToFillBnplTosScreenUserAction.SHOWN,
+        TouchToFillBnplTosScreenUserAction.ACCEPTED,
+        TouchToFillBnplTosScreenUserAction.DISMISSED,
+        TouchToFillBnplTosScreenUserAction.WALLET_LINK_CLICKED,
+        TouchToFillBnplTosScreenUserAction.LEGAL_MESSAGE_LINK_CLICKED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface TouchToFillBnplTosScreenUserAction {
+        int SHOWN = 0;
+        int ACCEPTED = 1;
+        int DISMISSED = 2;
+        int WALLET_LINK_CLICKED = 3;
+        int LEGAL_MESSAGE_LINK_CLICKED = 4;
+        int MAX_VALUE = LEGAL_MESSAGE_LINK_CLICKED;
+    }
+
     @VisibleForTesting
     static final String TOUCH_TO_FILL_CREDIT_CARD_OUTCOME_HISTOGRAM =
             "Autofill.TouchToFill.CreditCard.Outcome2";
@@ -314,6 +332,22 @@
 
     @VisibleForTesting static final String ERROR_SCREEN_DISMISSED = ".ErrorScreen.Dismissed";
 
+    @VisibleForTesting static final String AFFIRM_TOS_SCREEN = ".AffirmTosScreen";
+
+    @VisibleForTesting static final String KLARNA_TOS_SCREEN = ".KlarnaTosScreen";
+
+    @VisibleForTesting static final String ZIP_TOS_SCREEN = ".ZipTosScreen";
+
+    @VisibleForTesting static final String SCREEN_SHOWN = ".Shown";
+
+    @VisibleForTesting static final String SCREEN_ACCEPTED = ".Accepted";
+
+    @VisibleForTesting static final String SCREEN_DISMISSED = ".Dismissed";
+
+    @VisibleForTesting static final String WALLET_LINK_CLICKED = ".WalletLinkClicked";
+
+    @VisibleForTesting static final String LEGAL_MESSAGE_LINK_CLICKED = ".LegalMessageLinkClicked";
+
     // LINT.ThenChange(/tools/metrics/actions/actions.xml)
 
     // LINT.IfChange
@@ -330,6 +364,7 @@
     private List<LoyaltyCard> mAffiliatedLoyaltyCards;
     private List<LoyaltyCard> mAllLoyaltyCards;
     private List<BnplIssuerContext> mBnplIssuerContexts;
+    private String mBnplIssuerIdWithTosShown;
     private Function<LoyaltyCard, Drawable> mValuableImageFunction;
     private BottomSheetFocusHelper mBottomSheetFocusHelper;
     private boolean mShouldShowScanCreditCard;
@@ -789,6 +824,9 @@
         mModel.set(CURRENT_SCREEN, BNPL_ISSUER_TOS_SCREEN);
         mModel.set(SHEET_ITEMS, sheetItems);
         mModel.set(VISIBLE, true);
+
+        mBnplIssuerIdWithTosShown = bnplIssuerTosDetail.getIssuerId();
+        recordTouchToFillBnplTosUserAction(TouchToFillBnplTosScreenUserAction.SHOWN);
     }
 
     void hideSheet() {
@@ -818,6 +856,9 @@
                     recordTouchToFillBnplUserAction(PROGRESS_SCREEN_DISMISSED);
                 } else if (mModel.get(CURRENT_SCREEN) == ERROR_SCREEN) {
                     recordTouchToFillBnplUserAction(ERROR_SCREEN_DISMISSED);
+                } else if (mModel.get(CURRENT_SCREEN) == BNPL_ISSUER_TOS_SCREEN) {
+                    recordTouchToFillBnplTosUserAction(
+                            TouchToFillBnplTosScreenUserAction.DISMISSED);
                 }
                 RecordHistogram.recordEnumeratedHistogram(
                         TOUCH_TO_FILL_CREDIT_CARD_OUTCOME_HISTOGRAM,
@@ -915,7 +956,13 @@
                 new SpanApplier.SpanInfo(
                         "<link>",
                         "</link>",
-                        new ChromeClickableSpan(mContext, view -> openLink(mContext, WALLET_URL))));
+                        new ChromeClickableSpan(
+                                mContext,
+                                view -> {
+                                    recordTouchToFillBnplTosUserAction(
+                                            TouchToFillBnplTosScreenUserAction.WALLET_LINK_CLICKED);
+                                    openLink(mContext, WALLET_URL);
+                                })));
     }
 
     // TODO(crbug.com/459842727): Split HOME_SCREEN into CREDIT_CARD_HOME_SCREEN,
@@ -1014,6 +1061,7 @@
         }
         showProgressScreen();
         mDelegate.onBnplTosAccepted();
+        recordTouchToFillBnplTosUserAction(TouchToFillBnplTosScreenUserAction.ACCEPTED);
     }
 
     private void showAllLoyaltyCards() {
@@ -1297,7 +1345,14 @@
                 TOS_FOOTER,
                 new PropertyModel.Builder(TosFooterProperties.ALL_KEYS)
                         .with(LEGAL_MESSAGE_LINES, legalMessageLines)
-                        .with(LINK_OPENER, url -> openLink(mContext, url))
+                        .with(
+                                LINK_OPENER,
+                                url -> {
+                                    recordTouchToFillBnplTosUserAction(
+                                            TouchToFillBnplTosScreenUserAction
+                                                    .LEGAL_MESSAGE_LINK_CLICKED);
+                                    openLink(mContext, url);
+                                })
                         .build());
     }
 
@@ -1360,6 +1415,48 @@
         }
     }
 
+    private void recordTouchToFillBnplTosUserAction(
+            @TouchToFillBnplTosScreenUserAction int userAction) {
+        String tosUserAction;
+        switch (mBnplIssuerIdWithTosShown) {
+            case "affirm":
+                tosUserAction = AFFIRM_TOS_SCREEN;
+                break;
+            case "klarna":
+                tosUserAction = KLARNA_TOS_SCREEN;
+                break;
+            case "zip":
+                tosUserAction = ZIP_TOS_SCREEN;
+                break;
+            default:
+                // Nothing is recorded for all other issuerId's.
+                return;
+        }
+
+        switch (userAction) {
+            case TouchToFillBnplTosScreenUserAction.SHOWN:
+                tosUserAction += SCREEN_SHOWN;
+                break;
+            case TouchToFillBnplTosScreenUserAction.ACCEPTED:
+                tosUserAction += SCREEN_ACCEPTED;
+                break;
+            case TouchToFillBnplTosScreenUserAction.DISMISSED:
+                tosUserAction += SCREEN_DISMISSED;
+                break;
+            case TouchToFillBnplTosScreenUserAction.WALLET_LINK_CLICKED:
+                tosUserAction += WALLET_LINK_CLICKED;
+                break;
+            case TouchToFillBnplTosScreenUserAction.LEGAL_MESSAGE_LINK_CLICKED:
+                tosUserAction += LEGAL_MESSAGE_LINK_CLICKED;
+                break;
+            default:
+                assert false : "Undefined BNPL ToS screen user action: " + userAction;
+                return;
+        }
+
+        recordTouchToFillBnplUserAction(tosUserAction);
+    }
+
     private static void recordTouchToFillBnplUserAction(String userAction) {
         RecordUserAction.record(TOUCH_TO_FILL_BNPL_USER_ACTION + userAction);
     }
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 0438743..fd35ad2 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2421,7 +2421,6 @@
       "//chromeos/ash/components/nearby/presence",
       "//chromeos/ash/components/nearby/presence/credentials",
       "//chromeos/ash/components/network",
-      "//chromeos/ash/components/network/portal_detector",
       "//chromeos/ash/components/nonclosable_app_ui",
       "//chromeos/ash/components/peripheral_notification",
       "//chromeos/ash/components/phonehub",
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachment.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachment.java
index e856377..acd4b81 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachment.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachment.java
@@ -28,6 +28,7 @@
     public final String mimeType;
     public final byte[] data;
     public final @Nullable Tab tab;
+    public final @Nullable Integer tabId;
     private @Nullable String mToken;
 
     private FuseboxAttachment(
@@ -42,7 +43,13 @@
         this.title = title;
         this.mimeType = mimeType;
         this.data = data;
-        this.tab = tab;
+        if (tab != null && tab.getId() != Tab.INVALID_TAB_ID) {
+            this.tab = tab;
+            this.tabId = tab.getId();
+        } else {
+            this.tab = null;
+            this.tabId = null;
+        }
         mToken = null;
 
         // Set the ATTACHMENT property to this instance after construction
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 b5eccf1..180d7d53a 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
@@ -9,8 +9,13 @@
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
+import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
 /**
  * A specialized ModelList for Fusebox attachments that maintains tight coupling with
  * ComposeBoxQueryController, coordinating all operations with the backend to ensure consistent
@@ -85,6 +90,38 @@
     }
 
     /**
+     * Removes FuseboxAttachments from the model list and backend that satisfy the given filter
+     * predicate.
+     *
+     * @param filter The predicate to test each {@link ListItem} against for removal.
+     * @return True if one or more attachments were removed; False, otherwise.
+     */
+    public boolean removeIf(Predicate<ListItem> filter) {
+        List<FuseboxAttachment> attachmentsToRemove = new ArrayList<>();
+
+        // Identify attachments that satisfy the filter.
+        for (int index = 0; index < size(); index++) {
+            FuseboxAttachment attachment = get(index);
+            if (filter.test(attachment)) {
+                attachmentsToRemove.add(attachment);
+            }
+        }
+
+        // If nothing was found, return false.
+        if (attachmentsToRemove.isEmpty()) {
+            return false;
+        }
+
+        // Execute removal using the existing single-item method.
+        for (FuseboxAttachment attachment : attachmentsToRemove) {
+            remove(attachment);
+        }
+
+        // Since we executed actual removal logic one or more times, return true.
+        return true;
+    }
+
+    /**
      * Removes all attachments from the model list and backend. This is the preferred method for
      * clearing all attachments.
      */
@@ -102,6 +139,17 @@
         super.clear();
     }
 
+    /**
+     * Retrieves the FuseboxAttachment at the specified position in this list.
+     *
+     * @param index index of the element to return.
+     * @return the FuseboxAttachment at the specified position.
+     */
+    @Override
+    public FuseboxAttachment get(int index) {
+        return (FuseboxAttachment) super.get(index);
+    }
+
     /** Apply a variant of the branded color scheme to Fusebox Attachment elements. */
     /*package */ void updateVisualsForState(@BrandedColorScheme int brandedColorScheme) {
         if (mBrandedColorScheme == brandedColorScheme) return;
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 736f955..808e75c 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
@@ -51,7 +51,9 @@
 import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /** Mediator for the Fusebox component. */
 @NullMarked
@@ -169,6 +171,12 @@
         mModelList.clear();
     }
 
+    /** Activate AI Mode if no other custom mode is already active. */
+    void maybeActivateAiMode(@AiModeActivationSource int activationReason) {
+        if (mAutocompleteRequestTypeSupplier.get() != AutocompleteRequestType.SEARCH) return;
+        activateAiMode(activationReason);
+    }
+
     /** Activate AI Mode as the Next Request fulfillment type. */
     void activateAiMode(@AiModeActivationSource int activationReason) {
         mPopup.dismiss();
@@ -268,7 +276,7 @@
 
     private void onAddCurrentTab(Tab tab) {
         if (mComposeBoxQueryControllerBridge == null) return;
-        activateAiMode(AiModeActivationSource.IMPLICIT);
+        maybeActivateAiMode(AiModeActivationSource.IMPLICIT);
 
         var attachment = FuseboxAttachment.forTab(tab, mContext.getResources());
 
@@ -307,10 +315,13 @@
             return;
         }
 
-        Long[] preselectedIds = getPreselectionTabIds();
+        Integer[] preselectedIntegerIds = getPreselectionTabIds();
+        Long[] preselectedIds = new Long[preselectedIntegerIds.length];
+        for (int i = 0; i < preselectedIntegerIds.length; i++) {
+            preselectedIds[i] = (long) preselectedIntegerIds[i];
+        }
 
         if (preselectedIds != null && preselectedIds.length > 0) {
-            // Send the IDs to the activity using the defined extra.
             intent.putExtra(EXTRA_PRESELECTED_TAB_IDS, preselectedIds);
         }
 
@@ -321,14 +332,47 @@
     void onTabPickerResult(int resultCode, @Nullable Intent data) {
         if (resultCode != Activity.RESULT_OK || data == null || data.getExtras() == null) return;
         // Retrieve list of tab ids.
-        long[] tabIds = data.getLongArrayExtra(EXTRA_ATTACHMENT_TAB_IDS);
-        if (tabIds == null) return;
-        for (long tabId : tabIds) {
-            TabModelSelector tabModelSelector = mTabModelSelectorSupplier.get();
-            if (tabModelSelector == null) return;
-            Tab tab = mTabModelSelectorSupplier.get().getTabById((int) tabId);
-            if (tab == null) break;
-            onAddCurrentTab(tab);
+        long[] selectedTabIds = data.getLongArrayExtra(EXTRA_ATTACHMENT_TAB_IDS);
+        if (selectedTabIds == null) {
+            selectedTabIds = new long[0];
+        }
+
+        Set<Integer> newSelectedIds = new HashSet<>();
+        for (long id : selectedTabIds) {
+            newSelectedIds.add((int) id);
+        }
+
+        updateCurrentlyAttachedTabs(newSelectedIds);
+    }
+
+    /**
+     * Reconciles the model list attachments with a new set of selected tab IDs by removing
+     * deselected tabs and adding newly selected tabs in one pass.
+     *
+     * @param newlySelectedTabIds The set of Tab IDs (as Integer) that are now selected by the user.
+     */
+    @VisibleForTesting
+    public void updateCurrentlyAttachedTabs(Set<Integer> newlySelectedTabIds) {
+        TabModelSelector tabModelSelector = mTabModelSelectorSupplier.get();
+        if (tabModelSelector == null) return;
+        Set<Integer> currentAttachedIds = new HashSet<Integer>();
+        Collections.addAll(currentAttachedIds, getPreselectionTabIds());
+        currentAttachedIds.remove(null);
+        mModelList.removeIf(
+                item -> {
+                    if (item.type != FuseboxAttachmentType.ATTACHMENT_TAB) return false;
+                    FuseboxAttachment attachment =
+                            item.model.get(FuseboxAttachmentProperties.ATTACHMENT);
+                    Integer tabId = assumeNonNull(attachment).tabId;
+                    return !newlySelectedTabIds.contains(tabId);
+                });
+
+        for (int id : newlySelectedTabIds) {
+            if (!currentAttachedIds.contains(id)) {
+                Tab tab = tabModelSelector.getTabById(id);
+                mModelList.add(
+                        FuseboxAttachment.forTab(assumeNonNull(tab), mContext.getResources()));
+            }
         }
     }
 
@@ -503,7 +547,7 @@
      * @param attachmentDetails The details of the attachment to add.
      */
     /* package */ void uploadAndAddAttachment(FuseboxAttachment attachment) {
-        activateAiMode(AiModeActivationSource.IMPLICIT);
+        maybeActivateAiMode(AiModeActivationSource.IMPLICIT);
 
         // Use FuseboxModelList's unified add method
         mModelList.add(attachment);
@@ -547,27 +591,18 @@
     }
 
     /**
-     * @return Array of Tab IDs (as Long[]), empty if no attachments.
+     * @return Array of Tab IDs (as Integer[]), empty if no attachments.
      */
-    public Long[] getPreselectionTabIds() {
-        List<Long> attachedTabIds = new ArrayList<>();
+    public Integer[] getPreselectionTabIds() {
+        List<Integer> attachedTabIds = new ArrayList<>();
 
         for (int i = 0; i < mModelList.size(); i++) {
-            MVCListAdapter.ListItem listItem = mModelList.get(i);
-            if (listItem.type != FuseboxAttachmentType.ATTACHMENT_TAB) {
+            FuseboxAttachment attachment = mModelList.get(i);
+            if (attachment.type != FuseboxAttachmentType.ATTACHMENT_TAB) {
                 continue;
             }
-
-            FuseboxAttachment attachment =
-                    listItem.model.get(FuseboxAttachmentProperties.ATTACHMENT);
-            @Nullable Tab tab = attachment.tab;
-
-            if (tab != null) {
-                attachedTabIds.add((long) tab.getId());
-            }
+            attachedTabIds.add(attachment.tabId);
         }
-
-        Long[] resultArray = attachedTabIds.toArray(new Long[0]);
-        return resultArray;
+        return attachedTabIds.toArray(new Integer[0]);
     }
 }
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 e56aca46..01537a0 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
@@ -55,6 +55,7 @@
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.omnibox.R;
+import org.chromium.chrome.browser.omnibox.fusebox.FuseboxAttachmentRecyclerViewAdapter.FuseboxAttachmentType;
 import org.chromium.chrome.browser.omnibox.fusebox.FuseboxMetrics.AiModeActivationSource;
 import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
 import org.chromium.chrome.browser.tab.Tab;
@@ -66,10 +67,13 @@
 import org.chromium.ui.base.MimeTypeUtils;
 import org.chromium.ui.base.TestActivity;
 import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modelutil.MVCListAdapter;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.url.GURL;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Function;
 
 /** Unit tests for {@link FuseboxMediator}. */
@@ -177,6 +181,42 @@
         mAttachments.add(FuseboxAttachment.forTab(mockTab, mResources));
     }
 
+    private void addTabAttachment(Tab tab) {
+        int id = tab.getId();
+        String title = tab.getTitle();
+
+        mAttachments.add(FuseboxAttachment.forTab(tab, mResources));
+    }
+
+    private Tab mockTab(int id, boolean webContentsReady) {
+        Tab tab = mock(Tab.class);
+        String token = "token-" + id;
+        when(tab.getId()).thenReturn(id);
+        when(tab.getWebContents()).thenReturn(webContentsReady ? mWebContents : null);
+        when(tab.getTitle()).thenReturn("Tab " + id);
+        when(mTabModelSelector.getTabById(id)).thenReturn(tab);
+
+        when(mComposeBoxQueryControllerBridge.addTabContext(tab)).thenReturn(token);
+        when(mComposeBoxQueryControllerBridge.addTabContextFromCache(id)).thenReturn(token);
+        return tab;
+    }
+
+    private Set<Integer> getCurrentlyAttachedIdsFromModel() {
+        Set<Integer> ids = new HashSet<>();
+        for (int i = 0; i < mAttachments.size(); i++) {
+            MVCListAdapter.ListItem listItem = mAttachments.get(i);
+            if (listItem.type != FuseboxAttachmentType.ATTACHMENT_TAB) continue;
+
+            FuseboxAttachment attachment =
+                    listItem.model.get(FuseboxAttachmentProperties.ATTACHMENT);
+            Tab tab = attachment.tab;
+            if (tab != null) {
+                ids.add(tab.getId());
+            }
+        }
+        return ids;
+    }
+
     @Test
     public void testDestroy() {
         assertTrue(mAutocompleteRequestTypeSupplier.hasObservers());
@@ -365,6 +405,24 @@
     }
 
     @Test
+    public void maybeActivateAiMode_takesEffectInSearchMode() {
+        mAutocompleteRequestTypeSupplier.set(AutocompleteRequestType.SEARCH);
+        mMediator.maybeActivateAiMode(AiModeActivationSource.DEDICATED_BUTTON);
+        assertEquals(
+                AutocompleteRequestType.AI_MODE,
+                (int) mModel.get(FuseboxProperties.AUTOCOMPLETE_REQUEST_TYPE));
+    }
+
+    @Test
+    public void maybeActivateAiMode_doesNotAlterCurrentCustomMode() {
+        mAutocompleteRequestTypeSupplier.set(AutocompleteRequestType.IMAGE_GENERATION);
+        mMediator.maybeActivateAiMode(AiModeActivationSource.DEDICATED_BUTTON);
+        assertEquals(
+                AutocompleteRequestType.IMAGE_GENERATION,
+                (int) mModel.get(FuseboxProperties.AUTOCOMPLETE_REQUEST_TYPE));
+    }
+
+    @Test
     public void setToolbarVisible_noBridge_doesNothing() {
         // Create a mediator, but don't initialize the bridge.
         FuseboxMediator mediator =
@@ -600,4 +658,41 @@
         assertFalse(mModel.get(FuseboxProperties.COMPACT_UI));
         assertFalse(mCompactModeEnabled);
     }
+
+    @Test
+    public void testUpdateCurrentlyAttachedTabs_Reconciliation() {
+        // Setup Tabs for the TabModelSelector
+        Tab tab1 = mockTab(101, true);
+        Tab tab2 = mockTab(102, false);
+        Tab tab3 = mockTab(103, true);
+        Tab tab4 = mockTab(104, false);
+
+        addTabAttachment(tab1);
+        addTabAttachment(tab3);
+        // Verify initial setup.
+        assertEquals(2, mAttachments.size());
+        assertTrue(getCurrentlyAttachedIdsFromModel().contains(101));
+        assertTrue(getCurrentlyAttachedIdsFromModel().contains(103));
+        clearInvocations(mMediator);
+
+        // Mock getPreselectionTabIds to return the current attached IDs (as Integer[]).
+        doReturn(new Integer[] {101, 103}).when(mMediator).getPreselectionTabIds();
+
+        // Create set of newly selected Ids.
+        Set<Integer> newlySelectedIds = new HashSet<>();
+        newlySelectedIds.add(102);
+        newlySelectedIds.add(103);
+        newlySelectedIds.add(104);
+
+        // Call updateCurrentlyAttachedTabs to add newly selected tabs and remove unselected tabs.
+        mMediator.updateCurrentlyAttachedTabs(newlySelectedIds);
+
+        // Verify final state.
+        Set<Integer> finalIds = getCurrentlyAttachedIdsFromModel();
+        assertEquals(3, finalIds.size());
+        assertFalse(finalIds.contains(101));
+        assertTrue(finalIds.contains(102));
+        assertTrue(finalIds.contains(103));
+        assertTrue(finalIds.contains(104));
+    }
 }
diff --git a/chrome/browser/ui/android/tabstrip/java/src/org/chromium/chrome/browser/tabstrip/TabStripTopControlLayer.java b/chrome/browser/ui/android/tabstrip/java/src/org/chromium/chrome/browser/tabstrip/TabStripTopControlLayer.java
index b54d377..95edf93 100644
--- a/chrome/browser/ui/android/tabstrip/java/src/org/chromium/chrome/browser/tabstrip/TabStripTopControlLayer.java
+++ b/chrome/browser/ui/android/tabstrip/java/src/org/chromium/chrome/browser/tabstrip/TabStripTopControlLayer.java
@@ -185,6 +185,15 @@
         }
     }
 
+    @Override
+    public void prepForHeightAdjustmentAnimation(int latestYOffset) {
+        if (mTabStrip == null) return;
+        // This is called after onTransitionRequested, which triggers the change in visibility.
+        // The offset tags needs to be set here, since the tab strip do not want to drive the
+        // animation using the offset tags.
+        mTabStrip.updateOffsetTagsInfo(null);
+    }
+
     // Implements TabStripTransitionHandler
 
     @Override
@@ -210,7 +219,6 @@
                         newHeight,
                         applyScrimOverlay,
                         onHeightTransitionStartCallback);
-        mTabStrip.updateOffsetTagsInfo(null);
     }
 
     @EnsuresNonNullIf("mTransitionState")
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java
index 0d45a0ba..47e57d3 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java
@@ -73,6 +73,7 @@
 
     private static final long PROGRESS_FRAME_TIME_CAP_MS = 50;
     private static final long ALPHA_ANIMATION_DURATION_MS = 140;
+    private static final long ANIMATION_DURATION_MS = 3000;
 
     /** Whether or not the progress bar has started processing updates. */
     private boolean mIsStarted;
@@ -80,6 +81,9 @@
     /** The target progress the smooth animation should move to (when animating smoothly). */
     private float mTargetProgress;
 
+    /** The current progress displayed by the animation. */
+    private float mAnimatedProgress;
+
     /** The logic used to animate the progress bar during smooth animation. */
     private final AnimationLogic mAnimationLogic;
 
@@ -164,6 +168,33 @@
                 });
     }
 
+    private final TimeAnimator mCompositedProgressBarAnimation = new TimeAnimator();
+
+    {
+        mCompositedProgressBarAnimation.setTimeListener(
+                new TimeListener() {
+                    @Override
+                    public void onTimeUpdate(
+                            TimeAnimator animation, long totalTimeMs, long deltaTimeMs) {
+                        if (MathUtils.areFloatsEqual(mAnimatedProgress, mTargetProgress)
+                                || mAnimatedProgress > mTargetProgress) {
+                            mCompositedProgressBarAnimation.cancel();
+                        }
+
+                        mAnimatedProgress += (deltaTimeMs / ((float) ANIMATION_DURATION_MS));
+                        mAnimatedProgress = Math.min(mAnimatedProgress, mTargetProgress);
+
+                        // TODO(mdjones): Find a sane way to have this call setProgressInternal so
+                        // the finish logic can be recycled. Consider stopping the progress
+                        // throttle if the smooth animation is running.
+                        ToolbarProgressBar.super.setProgress(mAnimatedProgress);
+
+                        // If progress is at 100%, start hiding the progress bar.
+                        if (MathUtils.areFloatsEqual(getProgress(), 1.f)) finish(true);
+                    }
+                });
+    }
+
     /**
      * Creates a toolbar progress bar.
      *
@@ -224,6 +255,11 @@
 
         mSmoothProgressAnimator.setTimeListener(null);
         mSmoothProgressAnimator.cancel();
+
+        if (shouldAnimateCompositedLayer()) {
+            mCompositedProgressBarAnimation.setTimeListener(null);
+            mCompositedProgressBarAnimation.cancel();
+        }
     }
 
     @Override
@@ -250,6 +286,7 @@
         postDelayed(mStartSmoothIndeterminate, ANIMATION_START_THRESHOLD);
 
         super.setProgress(0.0f);
+        mAnimatedProgress = 0.0f;
         mAnimationLogic.reset(0.0f);
         animateAlphaTo(1.0f);
     }
@@ -284,9 +321,15 @@
         mTargetProgress = 0;
 
         removeCallbacks(mStartSmoothIndeterminate);
-        if (mAnimatingView != null) mAnimatingView.cancelAnimation();
+        if (mAnimatingView != null) {
+            mAnimatingView.cancelAnimation();
+        }
         mSmoothProgressAnimator.cancel();
 
+        if (shouldAnimateCompositedLayer()) {
+            mCompositedProgressBarAnimation.cancel();
+        }
+
         if (fadeOut) {
             postDelayed(() -> hideProgressBar(true), HIDE_DELAY_MS);
         } else {
@@ -316,7 +359,11 @@
      * @return Whether any animator that delays the showing of progress is running.
      */
     private boolean areProgressAnimatorsRunning() {
-        return mSmoothProgressAnimator.isRunning();
+        boolean isCompositedAnimatorRunning =
+                shouldAnimateCompositedLayer()
+                        ? mCompositedProgressBarAnimation.isRunning()
+                        : false;
+        return mSmoothProgressAnimator.isRunning() || isCompositedAnimatorRunning;
     }
 
     /**
@@ -374,9 +421,19 @@
         // smooth-indeterminate animation.
         removeCallbacks(mStartSmoothIndeterminate);
 
-        if (!mSmoothProgressAnimator.isRunning()) {
-            postDelayed(mStartSmoothIndeterminate, ANIMATION_START_THRESHOLD);
-            super.setProgress(mTargetProgress);
+        if (shouldAnimateCompositedLayer()) {
+            if (mAnimatingView != null && !mAnimatingView.isRunning()) {
+                postDelayed(mStartSmoothIndeterminate, ANIMATION_START_THRESHOLD);
+            }
+        } else {
+            if (!mSmoothProgressAnimator.isRunning()) {
+                postDelayed(mStartSmoothIndeterminate, ANIMATION_START_THRESHOLD);
+                super.setProgress(mTargetProgress);
+            }
+        }
+
+        if (shouldAnimateCompositedLayer() && !mCompositedProgressBarAnimation.isRunning()) {
+            mCompositedProgressBarAnimation.start();
         }
 
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarTest.java
index 982763ae..a970c83 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarTest.java
@@ -7,6 +7,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -212,14 +214,16 @@
         assertTrue("Composited progress bar should be visible.", isCompositedProgressBarVisible());
 
         // Ensure progress updates reached 50%.
-        verify(mMockProgressBarObserver, times(1)).onVisibleProgressUpdated();
+        verify(mMockProgressBarObserver, atLeast(1)).onVisibleProgressUpdated();
+        clearInvocations(mMockProgressBarObserver);
         assertEquals("Progress should have reached 50%.", 0.5f, getProgress(), MathUtils.EPSILON);
 
         // Finish progress bar.
         mProgressBar.finish(true);
+        mShadowLooper.idle();
 
         // Ensure progress reached 100%.
-        verify(mMockProgressBarObserver, times(2)).onVisibleProgressUpdated();
+        verify(mMockProgressBarObserver, atLeast(1)).onVisibleProgressUpdated();
         assertEquals("Progress should have reached 100%.", 1.0f, getProgress(), MathUtils.EPSILON);
 
         // Make sure the progress bar remains visible through completion.
@@ -234,7 +238,7 @@
 
         // Ensure that visibility changed now that progress has completed.
         assertFalse("Progress bar should not be visible.", isAndroidProgressBarVisible());
-        verify(mMockProgressBarObserver, times(2)).onCompositedLayersVisibilityChanged();
+        verify(mMockProgressBarObserver, times(1)).onCompositedLayersVisibilityChanged();
         assertFalse(
                 "Composited progress bar should not be visible.", isCompositedProgressBarVisible());
     }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
index acebcb5..b488c62 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
@@ -13,7 +13,6 @@
 import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.supplier.SupplierUtils;
-import org.chromium.base.supplier.TransitiveObservableSupplier;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.browser_controls.BottomControlsStacker;
@@ -64,10 +63,9 @@
 
     private final ObservableSupplierImpl<BottomControlsContentDelegate> mContentDelegateWrapper =
             new ObservableSupplierImpl<>();
-    private final TransitiveObservableSupplier<BottomControlsContentDelegate, Boolean>
-            mHandleBackPressChangedSupplier =
-                    new TransitiveObservableSupplier<>(
-                            mContentDelegateWrapper, cd -> cd.getHandleBackPressChangedSupplier());
+    private final ObservableSupplier<Boolean> mHandleBackPressChangedSupplier =
+            mContentDelegateWrapper.createTransitive(
+                    BackPressHandler::getHandleBackPressChangedSupplier);
 
     private final ScrollingBottomViewResourceFrameLayout mRootFrameLayout;
     private final ScrollingBottomViewSceneLayer mSceneLayer;
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
index 35f265c..b81546d 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
@@ -9,7 +9,6 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
-import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.ImageButton;
 
 import androidx.annotation.ColorInt;
@@ -904,9 +903,20 @@
         // Skip the layout params in non-resting position to avoid trigger layout during browser
         // controls reposition.
         if (reachRestingPosition) {
-            MarginLayoutParams lp = (MarginLayoutParams) mToolbarLayout.getLayoutParams();
-            lp.topMargin = getTabStripHeight();
-            mToolbarLayout.setLayoutParams(lp);
+            mControlContainer.mutateToolbarLayoutParams().topMargin = getTabStripHeight();
         }
     }
+
+    @Override
+    public void prepForHeightAdjustmentAnimation(int latestYOffset) {
+        if (mBrowserControls.getControlsPosition() != ControlsPosition.TOP
+                || !BrowserControlsUtils.isTopControlsRefactorOffsetEnabled()
+                || mOverlayCoordinator == null) {
+            return;
+        }
+
+        // Remove the offset tag on animation starts, so the toolbar does not set the yOffset
+        // while the compositor moves the layer with offset tags.
+        mOverlayCoordinator.setOffsetTagInfo(null);
+    }
 }
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 7956476..5a04178 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
@@ -4,16 +4,12 @@
 
 package org.chromium.chrome.browser.toolbar.top;
 
-import android.animation.TimeAnimator;
-import android.animation.TimeAnimator.TimeListener;
 import android.content.Context;
-import android.graphics.Rect;
 import android.view.View;
 
 import androidx.annotation.ColorInt;
 
 import org.chromium.base.Callback;
-import org.chromium.base.MathUtils;
 import org.chromium.base.ResettersForTesting;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.build.annotations.NullMarked;
@@ -124,30 +120,6 @@
     private @Nullable OffsetTag mTopProgressBarOffsetTag;
     private @Nullable OffsetTag mBottomProgressBarOffsetTag;
 
-    private float mAnimatedProgress;
-    private float mTargetProgress;
-    private static final long ANIMATION_DURATION_MS = 3000;
-
-    private final TimeAnimator mProgressBarAnimation = new TimeAnimator();
-
-    {
-        mProgressBarAnimation.setTimeListener(
-                new TimeListener() {
-                    @Override
-                    public void onTimeUpdate(
-                            TimeAnimator animation, long totalTimeMs, long deltaTimeMs) {
-                        if (MathUtils.areFloatsEqual(mAnimatedProgress, mTargetProgress)
-                                || mAnimatedProgress > mTargetProgress) {
-                            return;
-                        }
-
-                        mAnimatedProgress += (deltaTimeMs / ((float) ANIMATION_DURATION_MS));
-                        mAnimatedProgress = Math.min(mAnimatedProgress, mTargetProgress);
-                        updateProgress();
-                    }
-                });
-    }
-
     TopToolbarOverlayMediator(
             PropertyModel model,
             Context context,
@@ -212,7 +184,7 @@
                             public void onLoadProgressChanged(Tab tab, float progress) {
                                 if (ChromeFeatureList.sAndroidAnimatedProgressBarInBrowser
                                         .isEnabled()) {
-                                    mTargetProgress = progress;
+                                    return;
                                 }
                                 updateProgress();
                             }
@@ -493,30 +465,6 @@
             onProgressBarOffsetTagsChanged();
         }
 
-        if (ChromeFeatureList.sAndroidAnimatedProgressBarInBrowser.isEnabled()) {
-            if (drawingInfo.visible && !mProgressBarAnimation.isStarted()) {
-                mAnimatedProgress = 0;
-                mProgressBarAnimation.start();
-            } else if (!drawingInfo.visible) {
-                mProgressBarAnimation.cancel();
-            }
-
-            Rect foregroundRect = drawingInfo.progressBarRect;
-            Rect backgroundRect = drawingInfo.progressBarBackgroundRect;
-            Rect staticBackgroundRect = drawingInfo.progressBarStaticBackgroundRect;
-            int progressX =
-                    foregroundRect.left
-                            + Math.round(mAnimatedProgress * staticBackgroundRect.width());
-            int gap = backgroundRect.left - foregroundRect.right;
-            drawingInfo.progressBarRect.set(
-                    foregroundRect.left, foregroundRect.top, progressX, foregroundRect.bottom);
-            drawingInfo.progressBarBackgroundRect.set(
-                    progressX + gap,
-                    backgroundRect.top,
-                    backgroundRect.right,
-                    backgroundRect.bottom);
-        }
-
         // TODO(https://crbug.com/439461293) Try not updating the model if nothing changed.
         mModel.set(
                 TopToolbarOverlayProperties.PROGRESS_BAR_INFO,
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 a5290b8..fa73e85 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
@@ -130,7 +130,6 @@
         setTabSupplierTab(mTab);
 
         verify(mProgressBar).addObserver(mProgressBarObserverCaptor.capture());
-
         verify(mTab).addObserver(mTabObserverCaptor.capture());
         verify(mBrowserControlsStateProvider).addObserver(mBrowserControlsObserverCaptor.capture());
         verify(mLayoutStateProvider).addObserver(mLayoutObserverCaptor.capture());
@@ -286,8 +285,8 @@
 
         mTabObserverCaptor.getValue().onLoadProgressChanged(mTab, 0.25f);
 
-        assertNotNull(
-                "The progress bar data should be populated.",
+        assertNull(
+                "The progress bar data should be null.",
                 mModel.get(TopToolbarOverlayProperties.PROGRESS_BAR_INFO));
 
         // Ensure the progress is correct on tab switch.
diff --git a/chrome/browser/ui/ash/login/captive_portal_window_browsertest.cc b/chrome/browser/ui/ash/login/captive_portal_window_browsertest.cc
index 484ac672..44b866e 100644
--- a/chrome/browser/ui/ash/login/captive_portal_window_browsertest.cc
+++ b/chrome/browser/ui/ash/login/captive_portal_window_browsertest.cc
@@ -14,7 +14,6 @@
 #include "chrome/browser/ash/login/test/js_checker.h"
 #include "chrome/browser/ash/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
-#include "chrome/browser/ash/net/network_portal_detector_test_impl.h"
 #include "chrome/browser/ui/ash/login/captive_portal_window_proxy.h"
 #include "chrome/browser/ui/ash/login/login_display_host.h"
 #include "chrome/browser/ui/webui/ash/login/error_screen_handler.h"
@@ -73,17 +72,10 @@
         std::make_unique<CaptivePortalWindowProxy>(web_contents);
   }
 
-  void SetUpInProcessBrowserTestFixture() override {
-    network_portal_detector_ = new NetworkPortalDetectorTestImpl();
-    network_portal_detector::InitializeForTesting(network_portal_detector_);
-  }
-
   void TearDownOnMainThread() override { captive_portal_window_proxy_.reset(); }
 
  private:
   std::unique_ptr<CaptivePortalWindowProxy> captive_portal_window_proxy_;
-  raw_ptr<NetworkPortalDetectorTestImpl, DanglingUntriaged>
-      network_portal_detector_;
 };
 
 IN_PROC_BROWSER_TEST_F(CaptivePortalWindowTest, Show) {
diff --git a/chrome/browser/ui/ash/main_extra_parts/BUILD.gn b/chrome/browser/ui/ash/main_extra_parts/BUILD.gn
index 6ebd654..3446e73d 100644
--- a/chrome/browser/ui/ash/main_extra_parts/BUILD.gn
+++ b/chrome/browser/ui/ash/main_extra_parts/BUILD.gn
@@ -91,7 +91,6 @@
     "//chromeos/ash/components/heatmap",
     "//chromeos/ash/components/login/readahead",
     "//chromeos/ash/components/network",
-    "//chromeos/ash/components/network/portal_detector",
     "//chromeos/ash/experiences/arc",
     "//chromeos/ash/services/bluetooth_config",
     "//chromeos/ash/services/bluetooth_config:in_process_bluetooth_config",
diff --git a/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.cc
index ddd51938..6c3c1d49 100644
--- a/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.cc
@@ -113,7 +113,6 @@
 #include "chromeos/ash/components/heatmap/heatmap_palm_detector_impl.h"
 #include "chromeos/ash/components/login/readahead/login_readahead_performer.h"
 #include "chromeos/ash/components/network/network_connect.h"
-#include "chromeos/ash/components/network/portal_detector/network_portal_detector.h"
 #include "chromeos/ash/experiences/arc/arc_features.h"
 #include "chromeos/ash/experiences/arc/window/arc_window_watcher.h"
 #include "chromeos/ash/services/bluetooth_config/fast_pair_delegate.h"
diff --git a/chrome/browser/ui/ash/network/BUILD.gn b/chrome/browser/ui/ash/network/BUILD.gn
index 43a273b..97d0dd05 100644
--- a/chrome/browser/ui/ash/network/BUILD.gn
+++ b/chrome/browser/ui/ash/network/BUILD.gn
@@ -48,7 +48,6 @@
     "//chromeos/ash/components/login/login_state",
     "//chromeos/ash/components/multidevice/logging",
     "//chromeos/ash/components/network",
-    "//chromeos/ash/components/network/portal_detector",
     "//chromeos/ash/components/tether",
     "//chromeos/constants",
     "//components/account_id",
diff --git a/chrome/browser/ui/ash/network/network_portal_signin_controller.cc b/chrome/browser/ui/ash/network/network_portal_signin_controller.cc
index 6a9e3e2..5ba35531e 100644
--- a/chrome/browser/ui/ash/network/network_portal_signin_controller.cc
+++ b/chrome/browser/ui/ash/network/network_portal_signin_controller.cc
@@ -22,7 +22,6 @@
 #include "chromeos/ash/components/network/network_event_log.h"
 #include "chromeos/ash/components/network/network_handler.h"
 #include "chromeos/ash/components/network/network_state_handler.h"
-#include "chromeos/ash/components/network/portal_detector/network_portal_detector.h"
 #include "chromeos/ash/components/network/proxy/proxy_config_service_impl.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/constants/pref_names.h"
diff --git a/chrome/browser/ui/ash/wm/float_controller_browsertest.cc b/chrome/browser/ui/ash/wm/float_controller_browsertest.cc
index 5aa9f848..ead107a 100644
--- a/chrome/browser/ui/ash/wm/float_controller_browsertest.cc
+++ b/chrome/browser/ui/ash/wm/float_controller_browsertest.cc
@@ -113,40 +113,3 @@
   ASSERT_TRUE(
       float_controller->IsFloatedWindowTuckedForTablet(browser_window2));
 }
-
-// Tests that flinging down from the client area to move a floated window does
-// not open the web ui tab strip.
-IN_PROC_BROWSER_TEST_F(FloatControllerBrowserTest,
-                       FlingDownDoesNotOpenTabStrip) {
-  ash::ShellTestApi().SetTabletModeEnabledForTest(true);
-
-  // A floated window is magnetized to the bottom right by default.
-  aura::Window* window = browser()->window()->GetNativeWindow();
-  ui::test::EventGenerator event_generator(window->GetRootWindow(), window);
-  event_generator.PressAndReleaseKeyAndModifierKeys(
-      ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
-  ASSERT_TRUE(ash::WindowState::Get(window)->IsFloated());
-  ASSERT_EQ(ash::FloatController::MagnetismCorner::kBottomRight,
-            ash::FloatTestApi::GetMagnetismCornerForBounds(
-                window->GetBoundsInScreen()));
-
-  // Drag the window up to the top right.
-  auto get_draggable_point = [](aura::Window* window) {
-    return gfx::Point(window->GetBoundsInScreen().CenterPoint().x(),
-                      window->GetBoundsInScreen().y() + 10);
-  };
-  event_generator.set_current_screen_location(get_draggable_point(window));
-  event_generator.PressMoveAndReleaseTouchBy(0, -200);
-  ASSERT_EQ(ash::FloatController::MagnetismCorner::kTopRight,
-            ash::FloatTestApi::GetMagnetismCornerForBounds(
-                window->GetBoundsInScreen()));
-
-  // Drag the window back down. Test that it doesn't open the tab strip.
-  event_generator.set_current_screen_location(get_draggable_point(window));
-  event_generator.PressMoveAndReleaseTouchBy(0, 200);
-  ASSERT_EQ(ash::FloatController::MagnetismCorner::kBottomRight,
-            ash::FloatTestApi::GetMagnetismCornerForBounds(
-                window->GetBoundsInScreen()));
-  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
-  EXPECT_FALSE(browser_view->webui_tab_strip()->GetVisible());
-}
diff --git a/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskImpl.java b/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskImpl.java
index 6708293..fd08142 100644
--- a/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskImpl.java
+++ b/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskImpl.java
@@ -16,7 +16,7 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
-import android.view.Window;
+import android.util.Pair;
 import android.view.WindowInsets;
 import android.view.WindowInsetsController;
 
@@ -100,6 +100,11 @@
         int DESTROYED = 5;
     }
 
+    /** Interface for logic using an {@link Activity} and its {@link ActivityWindowAndroid}. */
+    private interface ActivityUser<T> {
+        T use(Activity activity, ActivityWindowAndroid activityWindowAndroid);
+    }
+
     private final AtomicInteger mState = new AtomicInteger(State.UNKNOWN);
 
     private final PendingActionManager mPendingActionManager = new PendingActionManager();
@@ -162,13 +167,18 @@
 
                 @Override
                 public void onPrepare(WindowInsetsAnimationCompat animation) {
-                    synchronized (mActivityScopedObjectsLock) {
-                        var activityWindowAndroid =
-                                getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-                        if (activityWindowAndroid == null) return;
-                        mIsRestored = isRestoredInternalLocked(activityWindowAndroid);
-                        mBoundsBeforeAnimation = getCurrentBoundsInPxLocked(activityWindowAndroid);
-                    }
+                    useActivity(
+                            new ActivityUser<>() {
+                                @Override
+                                @GuardedBy("mActivityScopedObjectsLock")
+                                public Void use(
+                                        Activity activity,
+                                        ActivityWindowAndroid activityWindowAndroid) {
+                                    mIsRestored = isRestoredInternalLocked(activity);
+                                    mBoundsBeforeAnimation = getCurrentBoundsInPxLocked(activity);
+                                    return null;
+                                }
+                            });
                 }
 
                 @Override
@@ -183,15 +193,20 @@
 
                 @Override
                 public void onEnd(WindowInsetsAnimationCompat animation) {
-                    synchronized (mActivityScopedObjectsLock) {
-                        var activityWindowAndroid =
-                                getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-
-                        boolean isFullscreen = isFullscreenInternalLocked(activityWindowAndroid);
-                        if (mIsRestored && isFullscreen) {
-                            mRestoredBoundsInPx = mBoundsBeforeAnimation;
-                        }
-                    }
+                    useActivity(
+                            new ActivityUser<>() {
+                                @Override
+                                @GuardedBy("mActivityScopedObjectsLock")
+                                public Void use(
+                                        Activity activity,
+                                        ActivityWindowAndroid activityWindowAndroid) {
+                                    boolean isFullscreen = isFullscreenInternalLocked(activity);
+                                    if (mIsRestored && isFullscreen) {
+                                        mRestoredBoundsInPx = mBoundsBeforeAnimation;
+                                    }
+                                    return null;
+                                }
+                            });
                 }
             };
 
@@ -259,18 +274,20 @@
 
     @Override
     public void onNativeInitializationFinished() {
-        if (mPendingTaskInfo == null) return;
         synchronized (mActivityScopedObjectsLock) {
-            if (mActivityScopedObjects == null) return;
-            var activityWindowAndroid = mActivityScopedObjects.mActivityWindowAndroid;
+            if (mPendingTaskInfo == null || mActivityScopedObjects == null) {
+                return;
+            }
+
             // Transition from PENDING_CREATE to IDLE.
             assert mState.get() == State.PENDING_CREATE;
             assert mId == null;
 
-            mId = getActivity(activityWindowAndroid).getTaskId();
-
+            var activityWindowAndroid = mActivityScopedObjects.mActivityWindowAndroid;
+            var activity = getActivity(activityWindowAndroid);
+            mId = activity.getTaskId();
             mState.set(State.IDLE);
-            dispatchPendingActionsLocked(activityWindowAndroid);
+            dispatchPendingActionsLocked(activity, activityWindowAndroid);
 
             JniOnceCallback<Long> taskCreationCallbackForNative =
                     mPendingTaskInfo.mTaskCreationCallbackForNative;
@@ -286,7 +303,8 @@
     @Override
     public @Nullable ActivityWindowAndroid getActivityWindowAndroid() {
         synchronized (mActivityScopedObjectsLock) {
-            return getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
+            Pair<Activity, ActivityWindowAndroid> pair = getActivityAndWindowAndroidLocked();
+            return pair == null ? null : pair.second;
         }
     }
 
@@ -369,12 +387,16 @@
             return isActiveFuture;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            var activityWindowAndroid =
-                    getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-            if (activityWindowAndroid == null) return false;
-            return isActiveInternalLocked(activityWindowAndroid);
-        }
+        return useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Boolean use(
+                            Activity unused, ActivityWindowAndroid activityWindowAndroid) {
+                        return isActiveInternalLocked(activityWindowAndroid);
+                    }
+                },
+                /* defaultValue= */ false);
     }
 
     @Override
@@ -389,11 +411,15 @@
             return isMaximizedFuture;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            var activityWindowAndroid =
-                    getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-            return isMaximizedInternalLocked(activityWindowAndroid);
-        }
+        return useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Boolean use(Activity activity, ActivityWindowAndroid unused) {
+                        return isMaximizedInternalLocked(activity);
+                    }
+                },
+                /* defaultValue= */ false);
     }
 
     @Override
@@ -403,11 +429,15 @@
             return !isVisibleFuture;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            var activityWindowAndroid =
-                    getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-            return isMinimizedInternalLocked(activityWindowAndroid);
-        }
+        return useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Boolean use(Activity activity, ActivityWindowAndroid unused) {
+                        return isMinimizedInternalLocked(activity);
+                    }
+                },
+                /* defaultValue= */ false);
     }
 
     @Override
@@ -421,11 +451,15 @@
             return false;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            var activityWindowAndroid =
-                    getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-            return isFullscreenInternalLocked(activityWindowAndroid);
-        }
+        return useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Boolean use(Activity activity, ActivityWindowAndroid unused) {
+                        return isFullscreenInternalLocked(activity);
+                    }
+                },
+                /* defaultValue= */ false);
     }
 
     @Override
@@ -441,20 +475,22 @@
             return initialBounds;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            var activityWindowAndroid =
-                    getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-            if (activityWindowAndroid == null) {
-                return new Rect();
-            }
-
-            Rect restoredBoundsInPx =
-                    mRestoredBoundsInPx == null
-                            ? getCurrentBoundsInPxLocked(activityWindowAndroid)
-                            : mRestoredBoundsInPx;
-            return DisplayUtil.scaleToEnclosingRect(
-                    restoredBoundsInPx, 1.0f / activityWindowAndroid.getDisplay().getDipScale());
-        }
+        return useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Rect use(
+                            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
+                        Rect restoredBoundsInPx =
+                                mRestoredBoundsInPx == null
+                                        ? getCurrentBoundsInPxLocked(activity)
+                                        : mRestoredBoundsInPx;
+                        return DisplayUtil.scaleToEnclosingRect(
+                                restoredBoundsInPx,
+                                1.0f / activityWindowAndroid.getDisplay().getDipScale());
+                    }
+                },
+                /* defaultValue= */ new Rect());
     }
 
     @Override
@@ -482,15 +518,16 @@
             if (bounds != null) return bounds;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            var activityWindowAndroid =
-                    getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-            if (activityWindowAndroid == null) {
-                return new Rect();
-            }
-
-            return getCurrentBoundsInDpLocked(activityWindowAndroid);
-        }
+        return useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Rect use(
+                            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
+                        return getCurrentBoundsInDpLocked(activity, activityWindowAndroid);
+                    }
+                },
+                /* defaultValue= */ new Rect());
     }
 
     @Override
@@ -504,9 +541,16 @@
             return;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            showInternalLocked();
-        }
+        useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Void use(
+                            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
+                        showInternalLocked(activity, activityWindowAndroid);
+                        return null;
+                    }
+                });
     }
 
     @Override
@@ -514,12 +558,16 @@
         Boolean isVisible = mPendingActionManager.isVisibleFuture(mState.get());
         if (isVisible != null) return isVisible;
 
-        synchronized (mActivityScopedObjectsLock) {
-            var activityWindowAndroid =
-                    getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-            if (activityWindowAndroid == null) return false;
-            return isVisibleInternalLocked(activityWindowAndroid);
-        }
+        return useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Boolean use(
+                            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
+                        return isVisibleInternalLocked(activity);
+                    }
+                },
+                /* defaultValue= */ false);
     }
 
     @Override
@@ -533,16 +581,23 @@
             return;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            var activityWindowAndroid =
-                    getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-            if (activityWindowAndroid == null || !isActiveInternalLocked(activityWindowAndroid)) {
-                return;
-            }
-            mPendingActionManager.requestAction(PendingAction.SHOW_INACTIVE);
-            mState.set(State.PENDING_UPDATE);
-        }
-        ChromeAndroidTaskTrackerImpl.getInstance().activatePenultimatelyActivatedTask();
+        useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Void use(
+                            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
+                        if (!isActiveInternalLocked(activityWindowAndroid)) {
+                            return null;
+                        }
+
+                        mPendingActionManager.requestAction(PendingAction.SHOW_INACTIVE);
+                        mState.set(State.PENDING_UPDATE);
+                        ChromeAndroidTaskTrackerImpl.getInstance()
+                                .activatePenultimatelyActivatedTask();
+                        return null;
+                    }
+                });
     }
 
     @Override
@@ -557,25 +612,28 @@
         // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/res/Configuration.java;l=417-418;drc=64130047e019cee612a85dde07755efd8f356f12
         // Therefore, we obtain the new bounds using an Activity API (see
         // getBoundsInternalLocked()).
-        synchronized (mFeaturesLock) {
-            synchronized (mActivityScopedObjectsLock) {
-                var activityWindowAndroid =
-                        getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-                if (activityWindowAndroid == null) {
-                    return;
-                }
+        useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Void use(
+                            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
+                        var newBoundsInDp =
+                                getCurrentBoundsInDpLocked(activity, activityWindowAndroid);
+                        if (newBoundsInDp.equals(mLastBoundsInDpOnConfigChanged)) {
+                            return null;
+                        }
 
-                var newBoundsInDp = getCurrentBoundsInDpLocked(activityWindowAndroid);
-                if (newBoundsInDp.equals(mLastBoundsInDpOnConfigChanged)) {
-                    return;
-                }
+                        mLastBoundsInDpOnConfigChanged = newBoundsInDp;
+                        synchronized (mFeaturesLock) {
+                            for (var feature : mFeatures) {
+                                feature.onTaskBoundsChanged(newBoundsInDp);
+                            }
+                        }
 
-                mLastBoundsInDpOnConfigChanged = newBoundsInDp;
-                for (var feature : mFeatures) {
-                    feature.onTaskBoundsChanged(newBoundsInDp);
-                }
-            }
-        }
+                        return null;
+                    }
+                });
     }
 
     @Override
@@ -585,9 +643,15 @@
             return;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            closeInternalLocked();
-        }
+        useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Void use(Activity activity, ActivityWindowAndroid unused) {
+                        closeInternalLocked(activity);
+                        return null;
+                    }
+                });
     }
 
     @Override
@@ -599,14 +663,18 @@
             return;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            var activityWindowAndroid =
-                    getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-            if (activityWindowAndroid == null || isActiveInternalLocked(activityWindowAndroid)) {
-                return;
-            }
-            activateInternalLocked();
-        }
+        useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Void use(
+                            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
+                        if (!isActiveInternalLocked(activityWindowAndroid)) {
+                            activateInternalLocked(activity);
+                        }
+                        return null;
+                    }
+                });
     }
 
     @Override
@@ -618,16 +686,23 @@
             return;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            var activityWindowAndroid =
-                    getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-            if (activityWindowAndroid == null || !isActiveInternalLocked(activityWindowAndroid)) {
-                return;
-            }
-            mPendingActionManager.requestAction(PendingAction.DEACTIVATE);
-            mState.set(State.PENDING_UPDATE);
-        }
-        ChromeAndroidTaskTrackerImpl.getInstance().activatePenultimatelyActivatedTask();
+        useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Void use(Activity unused, ActivityWindowAndroid activityWindowAndroid) {
+                        if (!isActiveInternalLocked(activityWindowAndroid)) {
+                            return null;
+                        }
+
+                        mPendingActionManager.requestAction(PendingAction.DEACTIVATE);
+                        mState.set(State.PENDING_UPDATE);
+
+                        ChromeAndroidTaskTrackerImpl.getInstance()
+                                .activatePenultimatelyActivatedTask();
+                        return null;
+                    }
+                });
     }
 
     @Override
@@ -645,9 +720,16 @@
             return;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            maximizeInternalLocked();
-        }
+        useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Void use(
+                            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
+                        maximizeInternalLocked(activity, activityWindowAndroid);
+                        return null;
+                    }
+                });
     }
 
     @Override
@@ -664,9 +746,15 @@
             return;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            minimizeInternalLocked();
-        }
+        useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Void use(Activity activity, ActivityWindowAndroid unused) {
+                        minimizeInternalLocked(activity);
+                        return null;
+                    }
+                });
     }
 
     @Override
@@ -681,9 +769,16 @@
             return;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            restoreInternalLocked();
-        }
+        useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Void use(
+                            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
+                        restoreInternalLocked(activity, activityWindowAndroid);
+                        return null;
+                    }
+                });
     }
 
     @Override
@@ -695,11 +790,18 @@
             return;
         }
 
-        synchronized (mActivityScopedObjectsLock) {
-            mPendingActionManager.requestSetBounds(boundsInDp);
-            mState.set(State.PENDING_UPDATE);
-            setBoundsInDpInternalLocked(boundsInDp);
-        }
+        useActivity(
+                new ActivityUser<>() {
+                    @Override
+                    @GuardedBy("mActivityScopedObjectsLock")
+                    public Void use(
+                            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
+                        mPendingActionManager.requestSetBounds(boundsInDp);
+                        mState.set(State.PENDING_UPDATE);
+                        setBoundsInDpInternalLocked(activity, activityWindowAndroid, boundsInDp);
+                        return null;
+                    }
+                });
     }
 
     @Override
@@ -816,9 +918,11 @@
 
     @GuardedBy("mActivityScopedObjectsLock")
     @SuppressLint("NewApi")
-    private void dispatchPendingActionsLocked(ActivityWindowAndroid activityWindowAndroid) {
+    private void dispatchPendingActionsLocked(
+            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
         // Initiate actions on a live Task.
         assertAlive();
+
         Rect boundsInDp = mPendingActionManager.getPendingBoundsInDp();
         Rect restoredBoundsInDp = mPendingActionManager.getPendingRestoredBoundsInDp();
         @PendingAction int[] pendingActions = mPendingActionManager.getAndClearPendingActions();
@@ -826,7 +930,7 @@
             if (action == PendingAction.NONE) continue;
             switch (action) {
                 case PendingAction.SHOW:
-                    showInternalLocked();
+                    showInternalLocked(activity, activityWindowAndroid);
                     break;
                 case PendingAction.SHOW_INACTIVE:
                 case PendingAction.DEACTIVATE:
@@ -838,16 +942,16 @@
                     mShouldDispatchPendingDeactivate = true;
                     break;
                 case PendingAction.CLOSE:
-                    closeInternalLocked();
+                    closeInternalLocked(activity);
                     break;
                 case PendingAction.ACTIVATE:
-                    activateInternalLocked();
+                    activateInternalLocked(activity);
                     break;
                 case PendingAction.MAXIMIZE:
-                    maximizeInternalLocked();
+                    maximizeInternalLocked(activity, activityWindowAndroid);
                     break;
                 case PendingAction.MINIMIZE:
-                    minimizeInternalLocked();
+                    minimizeInternalLocked(activity);
                     break;
                 case PendingAction.RESTORE:
                     // RESTORE should be ignored to fall back to default startup bounds if
@@ -857,12 +961,12 @@
                                 DisplayUtil.scaleToEnclosingRect(
                                         restoredBoundsInDp,
                                         activityWindowAndroid.getDisplay().getDipScale());
-                        restoreInternalLocked();
+                        restoreInternalLocked(activity, activityWindowAndroid);
                     }
                     break;
                 case PendingAction.SET_BOUNDS:
                     assert boundsInDp != null;
-                    setBoundsInDpInternalLocked(boundsInDp);
+                    setBoundsInDpInternalLocked(activity, activityWindowAndroid, boundsInDp);
                     break;
                 default:
                     assert false : "Unsupported pending action.";
@@ -871,15 +975,17 @@
     }
 
     @GuardedBy("mActivityScopedObjectsLock")
-    private @Nullable ActivityWindowAndroid getActivityWindowAndroidInternalLocked(
-            boolean assertAlive) {
-        if (assertAlive) {
-            assertAlive();
-        }
-
-        return mActivityScopedObjects == null
+    private @Nullable Pair<Activity, ActivityWindowAndroid> getActivityAndWindowAndroidLocked() {
+        assertAlive();
+        var activityWindowAndroid =
+                mActivityScopedObjects == null
+                        ? null
+                        : mActivityScopedObjects.mActivityWindowAndroid;
+        var activity =
+                activityWindowAndroid == null ? null : activityWindowAndroid.getActivity().get();
+        return activityWindowAndroid == null || activity == null
                 ? null
-                : mActivityScopedObjects.mActivityWindowAndroid;
+                : Pair.create(activity, activityWindowAndroid);
     }
 
     private void clearActivityScopedObjectsInternal() {
@@ -910,6 +1016,22 @@
         }
     }
 
+    private void useActivity(ActivityUser<Void> user) {
+        synchronized (mActivityScopedObjectsLock) {
+            Pair<Activity, ActivityWindowAndroid> pair = getActivityAndWindowAndroidLocked();
+            if (pair != null) {
+                user.use(pair.first, pair.second);
+            }
+        }
+    }
+
+    private <T> T useActivity(ActivityUser<T> user, T defaultValue) {
+        synchronized (mActivityScopedObjectsLock) {
+            Pair<Activity, ActivityWindowAndroid> pair = getActivityAndWindowAndroidLocked();
+            return pair == null ? defaultValue : user.use(pair.first, pair.second);
+        }
+    }
+
     private void destroyFeatures() {
         synchronized (mFeaturesLock) {
             for (var feature : mFeatures) {
@@ -920,23 +1042,18 @@
     }
 
     @GuardedBy("mActivityScopedObjectsLock")
-    private Rect getCurrentBoundsInDpLocked(ActivityWindowAndroid activityWindowAndroid) {
-        Rect boundsInPx = getCurrentBoundsInPxLocked(activityWindowAndroid);
+    private Rect getCurrentBoundsInDpLocked(
+            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
+        Rect boundsInPx = getCurrentBoundsInPxLocked(activity);
         return convertBoundsInPxToDp(boundsInPx, activityWindowAndroid.getDisplay());
     }
 
-    @GuardedBy("mActivityScopedObjectsLock")
-    private Rect getCurrentBoundsInPxLocked(ActivityWindowAndroid activityWindowAndroid) {
+    private static Rect getCurrentBoundsInPxLocked(Activity activity) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
             Log.w(TAG, "getBoundsInPxLocked() requires Android R+; returning an empty Rect()");
             return new Rect();
         }
 
-        Activity activity = activityWindowAndroid.getActivity().get();
-        if (activity == null) {
-            return new Rect();
-        }
-
         return activity.getWindowManager().getCurrentWindowMetrics().getBounds();
     }
 
@@ -950,60 +1067,45 @@
                 : "This Task is neither pending create nor idle.";
     }
 
-    @GuardedBy("mActivityScopedObjectsLock")
-    private boolean isActiveInternalLocked(ActivityWindowAndroid activityWindowAndroid) {
+    private static boolean isActiveInternalLocked(ActivityWindowAndroid activityWindowAndroid) {
         return activityWindowAndroid.isTopResumedActivity();
     }
 
-    @GuardedBy("mActivityScopedObjectsLock")
     @RequiresApi(api = VERSION_CODES.R)
-    private boolean isRestoredInternalLocked(ActivityWindowAndroid activityWindowAndroid) {
-        return !isMinimizedInternalLocked(activityWindowAndroid)
-                && !isMaximizedInternalLocked(activityWindowAndroid)
-                && !isFullscreenInternalLocked(activityWindowAndroid);
+    private static boolean isRestoredInternalLocked(Activity activity) {
+        return !isMinimizedInternalLocked(activity)
+                && !isMaximizedInternalLocked(activity)
+                && !isFullscreenInternalLocked(activity);
     }
 
-    @GuardedBy("mActivityScopedObjectsLock")
-    private boolean isVisibleInternalLocked(@Nullable ActivityWindowAndroid activityWindowAndroid) {
-        if (activityWindowAndroid == null) return false;
-        return ApplicationStatus.isTaskVisible(getActivity(activityWindowAndroid).getTaskId());
+    private static boolean isVisibleInternalLocked(Activity activity) {
+        return ApplicationStatus.isTaskVisible(activity.getTaskId());
     }
 
-    @GuardedBy("mActivityScopedObjectsLock")
     @RequiresApi(api = VERSION_CODES.R)
-    private boolean isMaximizedInternalLocked(
-            @Nullable ActivityWindowAndroid activityWindowAndroid) {
-        if (activityWindowAndroid == null) return false;
-        var activity = activityWindowAndroid.getActivity().get();
-        if (activity == null) return false;
+    private static boolean isMaximizedInternalLocked(Activity activity) {
         if (activity.isInMultiWindowMode()) {
             // Desktop windowing mode is also a multi-window mode.
             Rect maxBoundsInPx =
                     ChromeAndroidTaskBoundsConstraints.getMaxBoundsInPx(
                             activity.getWindowManager());
-            return getCurrentBoundsInPxLocked(activityWindowAndroid).equals(maxBoundsInPx);
+            return getCurrentBoundsInPxLocked(activity).equals(maxBoundsInPx);
         } else {
             // In non-multi-window mode, Chrome is maximized by default.
             return true;
         }
     }
 
-    @GuardedBy("mActivityScopedObjectsLock")
-    private boolean isMinimizedInternalLocked(
-            @Nullable ActivityWindowAndroid activityWindowAndroid) {
-        return !isVisibleInternalLocked(activityWindowAndroid);
+    private static boolean isMinimizedInternalLocked(Activity activity) {
+        return !isVisibleInternalLocked(activity);
     }
 
-    @GuardedBy("mActivityScopedObjectsLock")
     @RequiresApi(api = VERSION_CODES.R)
-    private boolean isFullscreenInternalLocked(
-            @Nullable ActivityWindowAndroid activityWindowAndroid) {
-        if (activityWindowAndroid == null) return false;
-        Activity activity = activityWindowAndroid.getActivity().get();
-        if (activity == null) return false;
-        Window window = activity.getWindow();
+    private static boolean isFullscreenInternalLocked(Activity activity) {
+        var window = activity.getWindow();
         var windowManager = activity.getWindowManager();
-        /** See {@link CompositorViewHolder#isInFullscreenMode}. */
+
+        // See CompositorViewHolder#isInFullscreenMode
         return !windowManager
                         .getMaximumWindowMetrics()
                         .getWindowInsets()
@@ -1050,15 +1152,13 @@
     }
 
     @GuardedBy("mActivityScopedObjectsLock")
-    private void showInternalLocked() {
-        var activityWindowAndroid = getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-        if (activityWindowAndroid == null) return;
-        var activity = activityWindowAndroid.getActivity().get();
-        if (activity == null) return;
+    private void showInternalLocked(
+            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
         // No-op if already active.
         if (isActiveInternalLocked(activityWindowAndroid)) return;
+
         // Activate the Task if it's already visible.
-        if (isVisibleInternalLocked(activityWindowAndroid)) {
+        if (isVisibleInternalLocked(activity)) {
             ActivityManager activityManager =
                     (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
             mPendingActionManager.requestAction(PendingAction.SHOW);
@@ -1068,21 +1168,12 @@
     }
 
     @GuardedBy("mActivityScopedObjectsLock")
-    private void closeInternalLocked() {
-        var activityWindowAndroid = getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-        if (activityWindowAndroid == null) return;
-        Activity activity = activityWindowAndroid.getActivity().get();
-        if (activity == null) return;
+    private void closeInternalLocked(Activity activity) {
         activity.finishAndRemoveTask();
     }
 
     @GuardedBy("mActivityScopedObjectsLock")
-    private void activateInternalLocked() {
-        var activityWindowAndroid = getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-        if (activityWindowAndroid == null) return;
-        Activity activity = activityWindowAndroid.getActivity().get();
-        if (activity == null) return;
-
+    private void activateInternalLocked(Activity activity) {
         ActivityManager activityManager =
                 (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
         mPendingActionManager.requestAction(PendingAction.ACTIVATE);
@@ -1092,16 +1183,15 @@
 
     @GuardedBy("mActivityScopedObjectsLock")
     @RequiresApi(api = VERSION_CODES.R)
-    private void maximizeInternalLocked() {
-        var activityWindowAndroid = getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-        if (activityWindowAndroid == null) return;
-        Activity activity = activityWindowAndroid.getActivity().get();
-        if (activity == null) return;
+    private void maximizeInternalLocked(
+            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
         // No maximize action in non desktop window mode.
         if (!activity.isInMultiWindowMode()) return;
-        if (isRestoredInternalLocked(activityWindowAndroid)) {
-            mRestoredBoundsInPx = getCurrentBoundsInPxLocked(activityWindowAndroid);
+
+        if (isRestoredInternalLocked(activity)) {
+            mRestoredBoundsInPx = getCurrentBoundsInPxLocked(activity);
         }
+
         Rect maxBoundsInPx =
                 ChromeAndroidTaskBoundsConstraints.getMaxBoundsInPx(activity.getWindowManager());
         mPendingActionManager.requestMaximize(
@@ -1112,29 +1202,28 @@
 
     @GuardedBy("mActivityScopedObjectsLock")
     @RequiresApi(api = VERSION_CODES.R)
-    private void minimizeInternalLocked() {
-        var activityWindowAndroid = getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-        if (activityWindowAndroid == null || isMinimizedInternalLocked(activityWindowAndroid)) {
+    private void minimizeInternalLocked(Activity activity) {
+        if (isMinimizedInternalLocked(activity)) {
             return;
         }
-        if (isRestoredInternalLocked(activityWindowAndroid)) {
-            mRestoredBoundsInPx = getCurrentBoundsInPxLocked(activityWindowAndroid);
+        if (isRestoredInternalLocked(activity)) {
+            mRestoredBoundsInPx = getCurrentBoundsInPxLocked(activity);
         }
         mPendingActionManager.requestAction(PendingAction.MINIMIZE);
         mState.set(State.PENDING_UPDATE);
-        getActivity(activityWindowAndroid).moveTaskToBack(/* nonRoot= */ true);
+        activity.moveTaskToBack(/* nonRoot= */ true);
     }
 
     @RequiresApi(api = VERSION_CODES.R)
     @GuardedBy("mActivityScopedObjectsLock")
-    private void restoreInternalLocked() {
-        var activityWindowAndroid = getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-        if (activityWindowAndroid == null) return;
-        Activity activity = activityWindowAndroid.getActivity().get();
-        if (activity == null || mRestoredBoundsInPx == null) return;
-        if (isMinimizedInternalLocked(activityWindowAndroid)) {
-            activateInternalLocked();
+    private void restoreInternalLocked(
+            Activity activity, ActivityWindowAndroid activityWindowAndroid) {
+        if (mRestoredBoundsInPx == null) return;
+
+        if (isMinimizedInternalLocked(activity)) {
+            activateInternalLocked(activity);
         }
+
         mPendingActionManager.requestRestore(
                 convertBoundsInPxToDp(mRestoredBoundsInPx, activityWindowAndroid.getDisplay()));
         mState.set(State.PENDING_UPDATE);
@@ -1142,12 +1231,8 @@
     }
 
     @GuardedBy("mActivityScopedObjectsLock")
-    private void setBoundsInDpInternalLocked(Rect boundsInDp) {
-        var activityWindowAndroid = getActivityWindowAndroidInternalLocked(/* assertAlive= */ true);
-        if (activityWindowAndroid == null) return;
-        Activity activity = activityWindowAndroid.getActivity().get();
-        if (activity == null) return;
-
+    private void setBoundsInDpInternalLocked(
+            Activity activity, ActivityWindowAndroid activityWindowAndroid, Rect boundsInDp) {
         Rect boundsInPx =
                 DisplayUtil.scaleToEnclosingRect(
                         boundsInDp, activityWindowAndroid.getDisplay().getDipScale());
diff --git a/chrome/browser/ui/commerce/commerce_ui_tab_helper_unittest.cc b/chrome/browser/ui/commerce/commerce_ui_tab_helper_unittest.cc
index 570a4ab..e4585dfd 100644
--- a/chrome/browser/ui/commerce/commerce_ui_tab_helper_unittest.cc
+++ b/chrome/browser/ui/commerce/commerce_ui_tab_helper_unittest.cc
@@ -12,6 +12,11 @@
 #include "base/strings/string_util.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/time/default_clock.h"
+#include "chrome/browser/ui/browser_window/test/mock_browser_window_interface.h"
+#include "chrome/browser/ui/commerce/discounts_page_action_controller.h"
+#include "chrome/browser/ui/views/commerce/discounts_page_action_view_controller.h"
+#include "chrome/browser/ui/views/commerce/price_insights_page_action_view_controller.h"
+#include "chrome/browser/ui/views/page_action/test_support/mock_page_action_controller.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_util.h"
@@ -100,16 +105,32 @@
         .WillByDefault(testing::Return(web_contents_));
     ON_CALL(tab_interface_, GetUnownedUserDataHost())
         .WillByDefault(testing::ReturnRef(data_host_));
+    ON_CALL(tab_interface_, GetBrowserWindowInterface())
+        .WillByDefault(testing::Return(&browser_window_interface_));
+    ON_CALL(browser_window_interface_, CanShowCallToAction())
+        .WillByDefault(testing::Return(false));
+    ON_CALL(browser_window_interface_, GetUnownedUserDataHost())
+        .WillByDefault(testing::ReturnRef(data_host_));
 
     side_panel_registry_ = std::make_unique<SidePanelRegistry>(&tab_interface_);
     tab_helper_ = std::make_unique<commerce::CommerceUiTabHelper>(
         tab_interface_, shopping_service_.get(), bookmark_model_.get(),
         image_fetcher_.get(), side_panel_registry_.get());
+
+    discounts_page_action_controller_ =
+        std::make_unique<DiscountsPageActionViewController>(
+            tab_interface_, page_action_controller_, *tab_helper_.get());
+
+    price_insights_page_action_controller_ =
+        std::make_unique<PriceInsightsPageActionViewController>(
+            tab_interface_, page_action_controller_);
   }
 
   void TestBody() override {}
 
   void TearDown() override {
+    price_insights_page_action_controller_.reset();
+    discounts_page_action_controller_.reset();
     // Make sure the tab helper id destroyed before any of its dependencies are.
     tab_helper_.reset();
   }
@@ -159,6 +180,7 @@
   content::TestWebContentsFactory test_web_contents_factory_;
   raw_ptr<content::WebContents> web_contents_;
   ui::UnownedUserDataHost data_host_;
+  MockBrowserWindowInterface browser_window_interface_;
   tabs::MockTabInterface tab_interface_;
   std::unique_ptr<CommerceUiTabHelper> tab_helper_;
   std::unique_ptr<MockShoppingService> shopping_service_;
@@ -166,6 +188,11 @@
   std::unique_ptr<image_fetcher::MockImageFetcher> image_fetcher_;
   std::unique_ptr<SidePanelRegistry> side_panel_registry_;
   std::unique_ptr<MockAccountChecker> account_checker_;
+  page_actions::MockPageActionController page_action_controller_;
+  std::unique_ptr<DiscountsPageActionViewController>
+      discounts_page_action_controller_;
+  std::unique_ptr<PriceInsightsPageActionViewController>
+      price_insights_page_action_controller_;
   base::test::ScopedFeatureList features_;
 };
 
diff --git a/chrome/browser/ui/page_action/page_action_icon_type.cc b/chrome/browser/ui/page_action/page_action_icon_type.cc
index abecbb96..f061a0f 100644
--- a/chrome/browser/ui/page_action/page_action_icon_type.cc
+++ b/chrome/browser/ui/page_action/page_action_icon_type.cc
@@ -73,6 +73,12 @@
 }  // namespace
 
 bool IsPageActionMigrated(PageActionIconType page_action) {
+  if (!base::FeatureList::IsEnabled(features::kPageActionsMigration)) {
+    return false;
+  }
+
+  // Page actions on the new framework that don't have an implementation on the legacy path
+  // and don't have a feature param.
   if (page_action == PageActionIconType::kContextualSidePanel ||
       page_action == PageActionIconType::kJsOptimizations) {
     return true;
diff --git a/chrome/browser/ui/read_anything/read_anything_controller.cc b/chrome/browser/ui/read_anything/read_anything_controller.cc
index 0fa0ce2..67eece38 100644
--- a/chrome/browser/ui/read_anything/read_anything_controller.cc
+++ b/chrome/browser/ui/read_anything/read_anything_controller.cc
@@ -4,12 +4,15 @@
 
 #include "chrome/browser/ui/read_anything/read_anything_controller.h"
 
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_features.h"
 #include "chrome/browser/ui/tabs/public/tab_features.h"
 #include "chrome/browser/ui/tabs/tab_model.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry_id.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_ui.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/grit/generated_resources.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/accessibility/accessibility_features.h"
 
@@ -39,6 +42,26 @@
   return tab_->GetBrowserWindowInterface()->GetFeatures().side_panel_ui();
 }
 
+// Lazily creates and returns the WebUIContentsWrapper for Reading Mode.
+std::unique_ptr<WebUIContentsWrapperT<ReadAnythingUntrustedUI>>
+ReadAnythingController::GetOrCreateWebUIWrapper() {
+  if (!web_ui_wrapper_) {
+    Profile* profile = tab_->GetBrowserWindowInterface()->GetProfile();
+    web_ui_wrapper_ =
+        std::make_unique<WebUIContentsWrapperT<ReadAnythingUntrustedUI>>(
+            GURL(chrome::kChromeUIUntrustedReadAnythingSidePanelURL), profile,
+            IDS_READING_MODE_TITLE,
+            /*esc_closes_ui=*/false);
+  }
+  return std::move(web_ui_wrapper_);
+}
+
+void ReadAnythingController::SetWebUIWrapperForTest(
+    std::unique_ptr<WebUIContentsWrapperT<ReadAnythingUntrustedUI>>
+        web_ui_wrapper) {
+  web_ui_wrapper_ = std::move(web_ui_wrapper);
+}
+
 // TODO(crbug.com/447418049): Open immersive reading mode via this
 // entrypoint. Currently just open side panel reading mode via
 // ReadAnythingController when is_immersive_read_anything_enabled_ flag is
diff --git a/chrome/browser/ui/read_anything/read_anything_controller.h b/chrome/browser/ui/read_anything/read_anything_controller.h
index a34d96f9..ed23bdeb 100644
--- a/chrome/browser/ui/read_anything/read_anything_controller.h
+++ b/chrome/browser/ui/read_anything/read_anything_controller.h
@@ -9,6 +9,8 @@
 #include "chrome/browser/ui/views/side_panel/side_panel_entry_key.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_enums.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_ui.h"
+#include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.h"
+#include "chrome/browser/ui/webui/top_chrome/webui_contents_wrapper.h"
 #include "components/tabs/public/tab_interface.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "ui/base/unowned_user_data/scoped_unowned_user_data.h"
@@ -50,6 +52,16 @@
   // Returns the current presentation state of the Reading Mode feature.
   PresentationState GetPresentationState() const;
 
+  // Lazily creates and returns the WebUIContentsWrapper for the
+  // Reading Mode WebUI. Transfers ownership of the WebUIContentsWrapper to the
+  // caller.
+  std::unique_ptr<WebUIContentsWrapperT<ReadAnythingUntrustedUI>>
+  GetOrCreateWebUIWrapper();
+
+  void SetWebUIWrapperForTest(
+      std::unique_ptr<WebUIContentsWrapperT<ReadAnythingUntrustedUI>>
+          web_ui_wrapper);
+
  private:
   // Returns the SidePanelUI for the active tab if it can be shown.
   // Otherwise, returns nullptr.
@@ -57,6 +69,9 @@
 
   raw_ptr<tabs::TabInterface> tab_ = nullptr;
   ui::ScopedUnownedUserData<ReadAnythingController> scoped_unowned_user_data_;
+
+  std::unique_ptr<WebUIContentsWrapperT<ReadAnythingUntrustedUI>>
+      web_ui_wrapper_;
 };
 
 #endif  // CHROME_BROWSER_UI_READ_ANYTHING_READ_ANYTHING_CONTROLLER_H_
diff --git a/chrome/browser/ui/read_anything/read_anything_controller_browsertest.cc b/chrome/browser/ui/read_anything/read_anything_controller_browsertest.cc
index 30e5d2c..28aea9e 100644
--- a/chrome/browser/ui/read_anything/read_anything_controller_browsertest.cc
+++ b/chrome/browser/ui/read_anything/read_anything_controller_browsertest.cc
@@ -13,11 +13,16 @@
 #include "chrome/browser/ui/browser_window/public/browser_window_features.h"
 #include "chrome/browser/ui/tabs/public/tab_features.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/side_panel/side_panel.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_action_callback.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry_id.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_ui.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h"
+#include "chrome/browser/ui/webui/top_chrome/webui_contents_wrapper.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/accessibility_features.h"
 
 class ReadAnythingControllerBrowserTest : public InProcessBrowserTest {
@@ -156,3 +161,55 @@
            ReadAnythingController::PresentationState::kInSidePanel;
   }));
 }
+
+IN_PROC_BROWSER_TEST_F(ReadAnythingControllerBrowserTest,
+                       GetOrCreateWebUIWrapper) {
+  tabs::TabInterface* tab = browser()->tab_strip_model()->GetActiveTab();
+  ASSERT_TRUE(tab);
+  auto* controller = ReadAnythingController::From(tab);
+  ASSERT_TRUE(controller);
+
+  std::unique_ptr<WebUIContentsWrapperT<ReadAnythingUntrustedUI>> wrapper =
+      controller->GetOrCreateWebUIWrapper();
+  EXPECT_TRUE(wrapper);
+  EXPECT_TRUE(wrapper->web_contents());
+  EXPECT_TRUE(wrapper->web_contents()->GetWebUI());
+}
+
+IN_PROC_BROWSER_TEST_F(ReadAnythingControllerBrowserTest,
+                       WebUIContentsWrapperIsPassedToSidePanel) {
+  tabs::TabInterface* tab = browser()->tab_strip_model()->GetActiveTab();
+  ASSERT_TRUE(tab);
+  auto* controller = ReadAnythingController::From(tab);
+  ASSERT_TRUE(controller);
+
+  // Create the WebUI contents and get a pointer to it.
+  std::unique_ptr<WebUIContentsWrapperT<ReadAnythingUntrustedUI>> wrapper =
+      controller->GetOrCreateWebUIWrapper();
+  content::WebContents* controller_web_contents = wrapper->web_contents();
+  ASSERT_TRUE(controller_web_contents);
+
+  // Return the wrapper to the controller so it can be passed to the side panel.
+  controller->SetWebUIWrapperForTest(std::move(wrapper));
+
+  // Show Reading Mode.
+  controller->ShowUI(SidePanelOpenTrigger::kAppMenu);
+  auto* side_panel_ui = browser()->GetFeatures().side_panel_ui();
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return side_panel_ui->IsSidePanelEntryShowing(
+        SidePanelEntryKey(SidePanelEntryId::kReadAnything));
+  }));
+
+  // Get the WebContents from the side panel and assert it's the same one.
+  auto* side_panel = BrowserView::GetBrowserViewForBrowser(browser())
+                         ->contents_height_side_panel();
+  auto* content_wrapper = side_panel->GetContentParentView();
+  ASSERT_EQ(1u, content_wrapper->children().size());
+  auto* side_panel_view =
+      static_cast<SidePanelWebUIView*>(content_wrapper->children()[0]);
+  content::WebContents* side_panel_web_contents =
+      side_panel_view->web_contents();
+  ASSERT_TRUE(side_panel_web_contents);
+
+  EXPECT_EQ(controller_web_contents, side_panel_web_contents);
+}
diff --git a/chrome/browser/ui/read_anything/read_anything_side_panel_controller.cc b/chrome/browser/ui/read_anything/read_anything_side_panel_controller.cc
index 528bd2c..ee33f2c 100644
--- a/chrome/browser/ui/read_anything/read_anything_side_panel_controller.cc
+++ b/chrome/browser/ui/read_anything/read_anything_side_panel_controller.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/ui/actions/chrome_action_id.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_features.h"
+#include "chrome/browser/ui/read_anything/read_anything_controller.h"
 #include "chrome/browser/ui/read_anything/read_anything_service.h"
 #include "chrome/browser/ui/read_anything/read_anything_side_panel_web_view.h"
 #include "chrome/browser/ui/tabs/public/tab_features.h"
@@ -231,9 +232,15 @@
         ReadAnythingSidePanelControllerGlue::UserDataKey());
   }
 
-  auto web_view = std::make_unique<ReadAnythingSidePanelWebView>(
-      tab_->GetBrowserWindowInterface()->GetProfile(), scope);
-
+  std::unique_ptr<ReadAnythingSidePanelWebView> web_view;
+  if (features::IsImmersiveReadAnythingEnabled()) {
+    web_view = std::make_unique<ReadAnythingSidePanelWebView>(
+        tab_->GetBrowserWindowInterface()->GetProfile(), scope,
+        ReadAnythingController::From(tab_)->GetOrCreateWebUIWrapper());
+  } else {
+    web_view = std::make_unique<ReadAnythingSidePanelWebView>(
+        tab_->GetBrowserWindowInterface()->GetProfile(), scope);
+  }
   ReadAnythingSidePanelControllerGlue::CreateForWebContents(
       web_view->contents_wrapper()->web_contents(), this);
   web_view_ = web_view->GetWeakPtr();
diff --git a/chrome/browser/ui/read_anything/read_anything_side_panel_web_view.cc b/chrome/browser/ui/read_anything/read_anything_side_panel_web_view.cc
index 0178950f..6759b175 100644
--- a/chrome/browser/ui/read_anything/read_anything_side_panel_web_view.cc
+++ b/chrome/browser/ui/read_anything/read_anything_side_panel_web_view.cc
@@ -34,6 +34,16 @@
               IDS_READING_MODE_TITLE,
               /*esc_closes_ui=*/false)) {}
 
+ReadAnythingSidePanelWebView::ReadAnythingSidePanelWebView(
+    Profile* profile,
+    SidePanelEntryScope& scope,
+    std::unique_ptr<WebUIContentsWrapperT<ReadAnythingUntrustedUI>>
+        contents_wrapper)
+    : SidePanelWebUIViewT(scope,
+                          base::RepeatingClosure(),
+                          base::RepeatingClosure(),
+                          std::move(contents_wrapper)) {}
+
 content::WebContents* ReadAnythingSidePanelWebView::OpenURLFromTab(
     content::WebContents* source,
     const content::OpenURLParams& params,
diff --git a/chrome/browser/ui/read_anything/read_anything_side_panel_web_view.h b/chrome/browser/ui/read_anything/read_anything_side_panel_web_view.h
index e23a18e4..44cb6c8 100644
--- a/chrome/browser/ui/read_anything/read_anything_side_panel_web_view.h
+++ b/chrome/browser/ui/read_anything/read_anything_side_panel_web_view.h
@@ -19,7 +19,15 @@
   METADATA_HEADER(ReadAnythingSidePanelWebView,
                   SidePanelWebUIViewT_ReadAnythingUntrustedUI)
  public:
+  // Constructor for when this class creates its own WebUIContentsWrapper.
   ReadAnythingSidePanelWebView(Profile* profile, SidePanelEntryScope& scope);
+
+  // Constructor for when the WebUIContentsWrapper is passed to this class.
+  ReadAnythingSidePanelWebView(
+      Profile* profile,
+      SidePanelEntryScope& scope,
+      std::unique_ptr<WebUIContentsWrapperT<ReadAnythingUntrustedUI>>
+          contents_wrapper);
   ReadAnythingSidePanelWebView(const ReadAnythingSidePanelWebView&) = delete;
   ReadAnythingSidePanelWebView& operator=(const ReadAnythingSidePanelWebView&) =
       delete;
diff --git a/chrome/browser/ui/startup/BUILD.gn b/chrome/browser/ui/startup/BUILD.gn
index 9fa6107..b6ecfb3 100644
--- a/chrome/browser/ui/startup/BUILD.gn
+++ b/chrome/browser/ui/startup/BUILD.gn
@@ -32,7 +32,6 @@
   sources = [
     "bad_flags_prompt.h",
     "bidding_and_auction_consented_debugging_infobar_delegate.h",
-    "test_third_party_cookie_phaseout_infobar_delegate.h",
   ]
   public_deps = [
     "//components/infobars/core",
@@ -102,7 +101,6 @@
   sources = [
     "bad_flags_prompt.cc",
     "bidding_and_auction_consented_debugging_infobar_delegate.cc",
-    "test_third_party_cookie_phaseout_infobar_delegate.cc",
   ]
   deps = [
     ":startup",
@@ -357,9 +355,9 @@
       ":startup",
       "//base",
       "//base/test:test_support",
+      "//chrome/browser:shell_integration",
       "//chrome/browser/prefs:util",
       "//chrome/browser/search_engines",
-      "//chrome/browser:shell_integration",
       "//chrome/browser/ui/startup/focus:unit_tests",
       "//chrome/common",
       "//chrome/common:non_code_constants",
diff --git a/chrome/browser/ui/startup/infobar_utils.cc b/chrome/browser/ui/startup/infobar_utils.cc
index 251135a..e02a218 100644
--- a/chrome/browser/ui/startup/infobar_utils.cc
+++ b/chrome/browser/ui/startup/infobar_utils.cc
@@ -21,7 +21,6 @@
 #include "chrome/browser/ui/startup/obsolete_system_infobar_delegate.h"
 #include "chrome/browser/ui/startup/startup_browser_creator.h"
 #include "chrome/browser/ui/startup/startup_types.h"
-#include "chrome/browser/ui/startup/test_third_party_cookie_phaseout_infobar_delegate.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
@@ -29,7 +28,6 @@
 #include "components/prefs/pref_service.h"
 #include "content/public/common/content_switches.h"
 #include "google_apis/google_api_keys.h"
-#include "services/network/public/cpp/network_switches.h"
 
 #if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID)
 #include "chrome/browser/ui/startup/default_browser_prompt/default_browser_prompt.h"
@@ -97,9 +95,6 @@
 }
 #endif
 
-BASE_FEATURE(kShowTestThirdPartyCookiePhaseoutInfoBar,
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 }  // namespace
 
 void AddInfoBarsIfNecessary(BrowserWindowInterface* browser,
@@ -136,13 +131,6 @@
             switches::kProtectedAudiencesConsentedDebugToken)) {
       BiddingAndAuctionConsentedDebuggingDelegate::Create(web_contents);
     }
-
-    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-            network::switches::kTestThirdPartyCookiePhaseout) &&
-        base::FeatureList::IsEnabled(
-            kShowTestThirdPartyCookiePhaseoutInfoBar)) {
-      TestThirdPartyCookiePhaseoutInfoBarDelegate::Create(web_contents);
-    }
   }
 
   // Do not show any other info bars in Kiosk mode, because it's unlikely that
diff --git a/chrome/browser/ui/startup/test_third_party_cookie_phaseout_infobar_delegate.cc b/chrome/browser/ui/startup/test_third_party_cookie_phaseout_infobar_delegate.cc
deleted file mode 100644
index fc7d580..0000000
--- a/chrome/browser/ui/startup/test_third_party_cookie_phaseout_infobar_delegate.cc
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/startup/test_third_party_cookie_phaseout_infobar_delegate.h"
-
-#include <memory>
-#include <string>
-
-#include "chrome/grit/generated_resources.h"
-#include "components/infobars/content/content_infobar_manager.h"
-#include "components/vector_icons/vector_icons.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "url/gurl.h"
-
-#if BUILDFLAG(IS_ANDROID)
-#include "chrome/browser/infobars/confirm_infobar_creator.h"
-#include "components/infobars/core/infobar.h"
-#else
-#include "chrome/browser/devtools/global_confirm_info_bar.h"
-#endif
-
-// static
-void TestThirdPartyCookiePhaseoutInfoBarDelegate::Create(
-    content::WebContents* web_contents) {
-#if BUILDFLAG(IS_ANDROID)
-  infobars::ContentInfoBarManager::FromWebContents(web_contents)
-      ->AddInfoBar(CreateConfirmInfoBar(
-          std::make_unique<TestThirdPartyCookiePhaseoutInfoBarDelegate>()));
-#else
-  GlobalConfirmInfoBar::Show(
-      std::make_unique<TestThirdPartyCookiePhaseoutInfoBarDelegate>());
-#endif
-}
-
-const gfx::VectorIcon&
-TestThirdPartyCookiePhaseoutInfoBarDelegate::GetVectorIcon() const {
-  return vector_icons::kErrorOutlineIcon;
-}
-
-infobars::InfoBarDelegate::InfoBarIdentifier
-TestThirdPartyCookiePhaseoutInfoBarDelegate::GetIdentifier() const {
-  return TEST_THIRD_PARTY_COOKIE_PHASEOUT_DELEGATE;
-}
-
-std::u16string TestThirdPartyCookiePhaseoutInfoBarDelegate::GetMessageText()
-    const {
-  return l10n_util::GetStringUTF16(
-      IDS_TEST_THIRD_PARTY_COOKIE_BLOCKING_PHASEOUT_INFO);
-}
-
-std::u16string TestThirdPartyCookiePhaseoutInfoBarDelegate::GetLinkText()
-    const {
-  return l10n_util::GetStringUTF16(IDS_DISABLE);
-}
-
-GURL TestThirdPartyCookiePhaseoutInfoBarDelegate::GetLinkURL() const {
-  return GURL("chrome://flags/#test-third-party-cookie-phaseout");
-}
-
-bool TestThirdPartyCookiePhaseoutInfoBarDelegate::ShouldExpire(
-    const NavigationDetails& details) const {
-  return false;
-}
-
-bool TestThirdPartyCookiePhaseoutInfoBarDelegate::ShouldAnimate() const {
-  return false;
-}
-
-int TestThirdPartyCookiePhaseoutInfoBarDelegate::GetButtons() const {
-  return BUTTON_NONE;
-}
-
-bool TestThirdPartyCookiePhaseoutInfoBarDelegate::IsCloseable() const {
-  return true;
-}
diff --git a/chrome/browser/ui/startup/test_third_party_cookie_phaseout_infobar_delegate.h b/chrome/browser/ui/startup/test_third_party_cookie_phaseout_infobar_delegate.h
deleted file mode 100644
index 9a6dd071..0000000
--- a/chrome/browser/ui/startup/test_third_party_cookie_phaseout_infobar_delegate.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_STARTUP_TEST_THIRD_PARTY_COOKIE_PHASEOUT_INFOBAR_DELEGATE_H_
-#define CHROME_BROWSER_UI_STARTUP_TEST_THIRD_PARTY_COOKIE_PHASEOUT_INFOBAR_DELEGATE_H_
-
-#include <string>
-
-#include "components/infobars/core/confirm_infobar_delegate.h"
-#include "url/gurl.h"
-
-namespace content {
-class WebContents;
-}
-
-// An infobar for Chrome for Testing, which displays a message saying that when
-// the --test-third-party-cookie-phaseout switch is enabled that the 3PC
-// blocking settings are ignored.
-class TestThirdPartyCookiePhaseoutInfoBarDelegate
-    : public ConfirmInfoBarDelegate {
- public:
-  static void Create(content::WebContents* web_contents);
-
-  TestThirdPartyCookiePhaseoutInfoBarDelegate(
-      const TestThirdPartyCookiePhaseoutInfoBarDelegate&) = delete;
-  TestThirdPartyCookiePhaseoutInfoBarDelegate& operator=(
-      const TestThirdPartyCookiePhaseoutInfoBarDelegate&) = delete;
-  TestThirdPartyCookiePhaseoutInfoBarDelegate() = default;
-  ~TestThirdPartyCookiePhaseoutInfoBarDelegate() override = default;
-
- private:
-  infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override;
-  const gfx::VectorIcon& GetVectorIcon() const override;
-  std::u16string GetMessageText() const override;
-  std::u16string GetLinkText() const override;
-  GURL GetLinkURL() const override;
-  bool ShouldExpire(const NavigationDetails& details) const override;
-  bool ShouldAnimate() const override;
-  int GetButtons() const override;
-  bool IsCloseable() const override;
-};
-
-#endif  // CHROME_BROWSER_UI_STARTUP_TEST_THIRD_PARTY_COOKIE_PHASEOUT_INFOBAR_DELEGATE_H_
diff --git a/chrome/browser/ui/tabs/alert/tab_alert_controller_unittest.cc b/chrome/browser/ui/tabs/alert/tab_alert_controller_unittest.cc
index 3f48533ca..0733c38 100644
--- a/chrome/browser/ui/tabs/alert/tab_alert_controller_unittest.cc
+++ b/chrome/browser/ui/tabs/alert/tab_alert_controller_unittest.cc
@@ -76,6 +76,8 @@
         tab_strip_model_delegate_.get(), profile_);
     EXPECT_CALL(*browser_window_interface_, GetTabStripModel())
         .WillRepeatedly(testing::Return(tab_strip_model_.get()));
+    EXPECT_CALL(*browser_window_interface_, GetUnownedUserDataHost())
+        .WillRepeatedly(testing::ReturnRef(user_data_host_));
     std::unique_ptr<content::WebContents> web_contents =
         content::WebContentsTester::CreateTestWebContents(profile_, nullptr);
     tab_model_ = std::make_unique<TabModel>(std::move(web_contents),
@@ -114,6 +116,7 @@
   content::BrowserTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   content::RenderViewHostTestEnabler test_enabler_;
+  ui::UnownedUserDataHost user_data_host_;
   std::unique_ptr<TestingProfileManager> testing_profile_manager_;
   raw_ptr<Profile> profile_ = nullptr;
   std::unique_ptr<FakeBrowserWindowInterface> browser_window_interface_;
diff --git a/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.cc b/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.cc
index 89c8e6d..55499c2e 100644
--- a/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.cc
+++ b/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.cc
@@ -16,7 +16,10 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_features.h"
 #include "chrome/browser/ui/tab_ui_helper.h"
+#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_on_close_helper.h"
 #include "chrome/browser/ui/tabs/saved_tab_groups/tab_group_menu_utils.h"
+#include "chrome/browser/ui/tabs/tab_enums.h"
+#include "chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h"
 #include "chrome/browser/ui/tabs/tab_group_model.h"
 #include "chrome/browser/ui/tabs/tab_group_theme.h"
 #include "chrome/browser/ui/tabs/tab_menu_model_delegate.h"
@@ -57,6 +60,76 @@
   return base::ReplaceStringPlaceholders(format_string, short_title, nullptr);
 }
 
+// Ungroups all of the tabs specified by |tab_indices_to_close| and then tries
+// to close them and adds their URL to the end of the closed saved group
+// denoted by |closed_saved_group_id|. Note that if there are any saved groups
+// whose tabs are completely contained in |tab_indices_to_close|, we delete
+// these groups from the tab group sync service before proceeding.
+void MaybeDeleteTabsAndAddToSavedTabGroup(
+    std::vector<int> tab_indices_to_close,
+    base::Uuid closed_saved_group_id,
+    tab_groups::TabGroupSyncService* tab_group_sync_service,
+    TabStripModel* tab_strip_model) {
+  if (tab_indices_to_close.empty()) {
+    return;
+  }
+
+  if (!tab_group_sync_service->GetGroup(closed_saved_group_id)) {
+    return;
+  }
+
+  // Get any open groups that are covered by |tab_indices_to_close| and delete
+  // the saved groups they are in. Note that since we are in this function
+  // the user already agreed to having them deleted.
+  std::vector<tab_groups::TabGroupId> groups =
+      tab_strip_model->GetGroupsDestroyedFromRemovingIndices(
+          tab_indices_to_close);
+
+  for (const tab_groups::TabGroupId& local_group_id : groups) {
+    std::optional<tab_groups::SavedTabGroup> saved_group =
+        tab_group_sync_service->GetGroup(local_group_id);
+
+    if (saved_group) {
+      tab_group_sync_service->RemoveGroup(saved_group->saved_guid());
+    }
+  }
+
+  // Ungroup any tabs that are in open but not saved groups.
+  // Keep a vector of tab pointers in case the indices change after the
+  // ungrouping operation.
+  std::vector<tabs::TabInterface*> tab_ptrs_to_close;
+
+  for (int index : tab_indices_to_close) {
+    tab_ptrs_to_close.push_back(tab_strip_model->GetTabAtIndex(index));
+  }
+
+  tab_strip_model->RemoveFromGroup(tab_indices_to_close);
+
+  // Now that they are all ungrouped, close the tabs. But before doing that,
+  // have the tabs listen for when they close, so that they will add
+  // themselves to the saved group on closure.
+  for (tabs::TabInterface* tab_to_close : tab_ptrs_to_close) {
+    CHECK(tab_to_close);
+    tab_to_close->GetTabFeatures()->saved_tab_group_on_close_helper()->SetGroup(
+        closed_saved_group_id);
+    tab_to_close->Close();
+  }
+}
+
+// Callback function for when we use the tab group deletion dialog, if we see
+// it is possible for saved tab groups to be deleted from executing commands in
+// using the commands in ExistingTabGroupSubMenuModel.
+void OnTabGroupDeletionDialogOK(
+    std::vector<int> tab_indices_to_close,
+    base::Uuid closed_saved_group_id,
+    tab_groups::TabGroupSyncService* tab_group_sync_service,
+    TabStripModel* tab_strip_model,
+    tab_groups::DeletionDialogController::DeletionDialogTiming timing) {
+  MaybeDeleteTabsAndAddToSavedTabGroup(tab_indices_to_close,
+                                       closed_saved_group_id,
+                                       tab_group_sync_service, tab_strip_model);
+}
+
 }  // anonymous namespace
 
 ExistingTabGroupSubMenuModel::ExistingTabGroupSubMenuModel(
@@ -327,15 +400,16 @@
     return;
   }
 
-  base::RecordAction(base::UserMetricsAction("TabContextMenu_NewTabInGroup"));
-
   tab_groups::EitherGroupID group_v =
       target_index_to_group_mapping_.at(target_index);
 
   if (std::holds_alternative<base::Uuid>(group_v)) {
     const base::Uuid& group_id = get<base::Uuid>(group_v);
     AddSelectedTabsToSavedGroup(group_id);
+    base::RecordAction(
+        base::UserMetricsAction("TabContextMenu_AddToClosedSavedGroup"));
   } else if (std::holds_alternative<tab_groups::LocalTabGroupID>(group_v)) {
+    base::RecordAction(base::UserMetricsAction("TabContextMenu_NewTabInGroup"));
     tab_groups::TabGroupId group = get<tab_groups::TabGroupId>(group_v);
     AddSelectedTabsToOpenGroup(group);
   } else {
@@ -392,11 +466,39 @@
   CHECK(group_opt.has_value());
 
   std::vector<int> selected_indices = GetSelectedIndices();
-  for (tabs::TabInterface* tab : model()->GetTabsAtIndices(selected_indices)) {
-    CHECK(tab);
-    tgss->AddUrl(group_opt->saved_guid(),
-                 tab->GetTabFeatures()->tab_ui_helper()->GetTitle(),
-                 tab->GetContents()->GetLastCommittedURL());
+
+  if (selected_indices.empty()) {
+    return;
+  }
+
+  std::vector<tab_groups::TabGroupId> groups_destroyed =
+      model()->GetGroupsDestroyedFromRemovingIndices(selected_indices);
+
+  if (groups_destroyed.empty()) {
+    MaybeDeleteTabsAndAddToSavedTabGroup(
+        selected_indices, group_opt->saved_guid(), tgss, model());
+  } else {
+    // We have saved tab groups that may be deleted.
+    // Show the tab group deletion dialog and delete the groups using
+    // a callback function.
+    tabs::TabInterface* tab_0 = model()->GetTabAtIndex(selected_indices[0]);
+    tab_groups::DeletionDialogController* deletion_dialog_controller =
+        tab_0->GetBrowserWindowInterface()
+            ->GetFeatures()
+            .tab_group_deletion_dialog_controller();
+
+    base::OnceCallback<void(
+        tab_groups::DeletionDialogController::DeletionDialogTiming)>
+        callback = base::BindOnce(&OnTabGroupDeletionDialogOK, selected_indices,
+                                  group_opt->saved_guid(), tgss, model());
+
+    tab_groups::DeletionDialogController::DialogMetadata saved_dialog_metadata(
+        tab_groups::DeletionDialogController::DialogType::DeleteSingle,
+        /*closing_group_count=*/groups_destroyed.size(),
+        /*closing_multiple_tabs=*/selected_indices.size() > 1);
+
+    deletion_dialog_controller->MaybeShowDialog(
+        saved_dialog_metadata, std::move(callback), std::nullopt);
   }
 }
 
diff --git a/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model_browsertest.cc b/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model_browsertest.cc
index 654881c..9fa8f163 100644
--- a/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model_browsertest.cc
+++ b/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model_browsertest.cc
@@ -13,16 +13,27 @@
 #include "chrome/browser/ui/browser_tab_menu_model_delegate.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_features.h"
+#include "chrome/browser/ui/tabs/public/tab_features.h"
+#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_on_close_helper.h"
 #include "chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_service_initialized_observer.h"
+#include "chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h"
 #include "chrome/browser/ui/tabs/tab_group_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/ui_features.h"
+#include "chrome/browser/ui/unload_controller.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
+#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/saved_tab_groups/public/tab_group_sync_service.h"
 #include "components/saved_tab_groups/test_support/saved_tab_group_test_utils.h"
 #include "components/tab_groups/tab_group_id.h"
 #include "components/tabs/public/tab_group.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/events/types/event_type.h"
 #include "url/gurl.h"
 
 namespace {
@@ -34,6 +45,7 @@
       browser->session_id(), browser->profile(), browser->app_controller(),
       tgss);
 }
+
 }  // namespace
 
 class ExistingTabGroupSubMenuModelTest : public InProcessBrowserTest {
@@ -354,30 +366,54 @@
   CloseBrowserSynchronously(browser_2);
 }
 
-class ExistingTabGroupSubMenuModelTabGroupMoreEntryPoints
+class ExistingTabGroupSubMenuModelClosedSavedGroupsTest
     : public InProcessBrowserTest {
  public:
-  ExistingTabGroupSubMenuModelTabGroupMoreEntryPoints() {
+  ExistingTabGroupSubMenuModelClosedSavedGroupsTest() {
     scoped_feature_list_.InitAndEnableFeature(
         features::kTabGroupMenuMoreEntryPoints);
   }
 
   void WaitForTabSyncServiceInitialization() {
-    tab_groups::TabGroupSyncService* tgss_service =
-        static_cast<tab_groups::TabGroupSyncService*>(
-            tab_groups::TabGroupSyncServiceFactory::GetForProfile(
-                browser()->profile()));
     // Make the observer
     tab_groups::TabGroupSyncServiceInitializedObserver tgss_observer{
-        tgss_service};
+        tab_group_sync_service()};
     tgss_observer.Wait();
   }
 
+  tab_groups::TabGroupSyncService* tab_group_sync_service() {
+    return tab_groups::TabGroupSyncServiceFactory::GetForProfile(
+        browser()->profile());
+  }
+
+  TabStripModel* tab_strip_model() { return browser()->tab_strip_model(); }
+
+  TabStrip* tabstrip() {
+    return views::AsViewClass<TabStripRegionView>(
+               browser()->GetBrowserView().tab_strip_view())
+        ->tab_strip();
+  }
+
+  tab_groups::DeletionDialogController* deletion_dialog_controller() {
+    return browser()
+        ->browser_window_features()
+        ->tab_group_deletion_dialog_controller();
+  }
+
+  TabStripController* controller() { return tabstrip()->controller(); }
+
+  ui::MouseEvent dummy_event = ui::MouseEvent(ui::EventType::kMousePressed,
+                                              gfx::PointF(),
+                                              gfx::PointF(),
+                                              base::TimeTicks::Now(),
+                                              0,
+                                              0);
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(ExistingTabGroupSubMenuModelTabGroupMoreEntryPoints,
+IN_PROC_BROWSER_TEST_F(ExistingTabGroupSubMenuModelClosedSavedGroupsTest,
                        ShowSubmenuWithOnlyOpenGroups) {
   chrome::AddTabAt(browser(), GURL("chrome://newtab"), /*index=*/-1,
                    /*foreground=*/true);
@@ -408,7 +444,7 @@
   EXPECT_EQ(tab_group_submenu_1.GetDisplayedGroupCount(), 1u);
 }
 
-IN_PROC_BROWSER_TEST_F(ExistingTabGroupSubMenuModelTabGroupMoreEntryPoints,
+IN_PROC_BROWSER_TEST_F(ExistingTabGroupSubMenuModelClosedSavedGroupsTest,
                        ShowSubmenuWithOnlyClosedGroups) {
   WaitForTabSyncServiceInitialization();
   std::unique_ptr<TabMenuModelDelegate> tab_menu_model_delegate =
@@ -442,7 +478,7 @@
       model, 0, tab_menu_model_delegate.get()));
 }
 
-IN_PROC_BROWSER_TEST_F(ExistingTabGroupSubMenuModelTabGroupMoreEntryPoints,
+IN_PROC_BROWSER_TEST_F(ExistingTabGroupSubMenuModelClosedSavedGroupsTest,
                        ShowClosedAndOpenGroups) {
   WaitForTabSyncServiceInitialization();
   std::unique_ptr<TabMenuModelDelegate> tab_menu_model_delegate =
@@ -502,3 +538,155 @@
   EXPECT_TRUE(ExistingTabGroupSubMenuModel::ShouldShowSubmenu(
       model, 3, tab_menu_model_delegate.get()));
 }
+
+IN_PROC_BROWSER_TEST_F(ExistingTabGroupSubMenuModelClosedSavedGroupsTest,
+                       AddEntireOpenSavedGroupToClosedSavedGroup) {
+  WaitForTabSyncServiceInitialization();
+
+  // Make a closed saved tab group.
+  tab_groups::SavedTabGroup closed_saved_group =
+      tab_groups::test::CreateTestSavedTabGroup(std::nullopt);
+  tab_group_sync_service()->AddGroup(closed_saved_group);
+
+  // Prepare tabs to add to the closed saved group. We will prepare to add
+  // the tabs of an entire group and a partial group. Both of these
+  // should be saved and open in the tab strip.
+  chrome::AddTabAt(browser(), GURL("chrome://newtab"), -1, false);
+  chrome::AddTabAt(browser(), GURL("chrome://newtab"), -1, false);
+  chrome::AddTabAt(browser(), GURL("chrome://newtab"), -1, false);
+
+  ASSERT_TRUE(tab_strip_model()->count() == 4);
+
+  tab_groups::TabGroupId blue = tab_strip_model()->AddToNewGroup({0});
+  tab_groups::TabGroupId green = tab_strip_model()->AddToNewGroup({1, 2});
+
+  // Check that these are also saved groups
+  ASSERT_TRUE(tab_group_sync_service()->GetGroup(blue));
+  ASSERT_TRUE(tab_group_sync_service()->GetGroup(green));
+
+  // Select all of |blue|, but only one tab of |green|.
+  controller()->SelectTab(0, dummy_event);
+  controller()->ExtendSelectionTo(1);
+
+  // Now add all these to the closed saved tab group with the submenu.
+  ASSERT_TRUE(controller()->IsActiveTab(1));
+  ASSERT_TRUE(controller()->IsTabSelected(0));
+  ASSERT_TRUE(controller()->IsTabSelected(1));
+  ASSERT_FALSE(controller()->IsTabSelected(2));
+
+  std::unique_ptr<TabMenuModelDelegate> delegate =
+      CreateTabMenuModelDelegate(browser());
+  ExistingTabGroupSubMenuModel tab_group_submenu(nullptr, delegate.get(),
+                                                 tab_strip_model(), 1);
+
+  // Only two groups are shown, the closed saved tab group and the group
+  // only partially covered by the selected tabs (namely |green|)
+  ASSERT_EQ(tab_group_submenu.GetDisplayedGroupCount(), 2u);
+  tab_group_submenu.ExecuteExistingCommandForTesting(1);
+
+  // Hit OK on the dialog if it is showing
+  if (deletion_dialog_controller()->IsShowingDialog()) {
+    deletion_dialog_controller()->SimulateOkButtonForTesting();
+  }
+
+  // Check that the group we selected entirely to add to the closed saved group
+  // is deleted, both locally and in the sync service. Check the same is not
+  // true for the group which we only partially added.
+
+  EXPECT_FALSE(tab_strip_model()->group_model()->ContainsTabGroup(blue));
+  EXPECT_TRUE(tab_strip_model()->group_model()->ContainsTabGroup(green));
+
+  EXPECT_FALSE(tab_group_sync_service()->GetGroup(blue));
+  EXPECT_TRUE(tab_group_sync_service()->GetGroup(green));
+
+  // Finally make sure our closed saved group was updated.
+  std::optional<tab_groups::SavedTabGroup> group =
+      tab_group_sync_service()->GetGroup(closed_saved_group.saved_guid());
+
+  ASSERT_TRUE(group.has_value());
+  ASSERT_EQ(group->saved_tabs().size(),
+            closed_saved_group.saved_tabs().size() + 2u);
+}
+
+IN_PROC_BROWSER_TEST_F(ExistingTabGroupSubMenuModelClosedSavedGroupsTest,
+                       AddEntireOpenSavedGroupTabGroupDeletionDialogCancel) {
+  WaitForTabSyncServiceInitialization();
+
+  // Make a closed saved tab group.
+  tab_groups::SavedTabGroup closed_saved_group =
+      tab_groups::test::CreateTestSavedTabGroup(std::nullopt);
+  tab_group_sync_service()->AddGroup(closed_saved_group);
+
+  // Make a local tab group.
+  chrome::AddTabAt(browser(), GURL("chrome://newtab"), -1, true);
+
+  ASSERT_EQ(tab_strip_model()->count(), 2);
+  tab_groups::TabGroupId blue = tab_strip_model()->AddToNewGroup({1});
+
+  tab_strip_model()->SelectTabAt(1);
+
+  // Add the local tab group to the closed saved tab group using the submenu.
+  // Make sure the dialog shows.
+  std::unique_ptr<TabMenuModelDelegate> delegate =
+      CreateTabMenuModelDelegate(browser());
+  ExistingTabGroupSubMenuModel tab_group_submenu(nullptr, delegate.get(),
+                                                 tab_strip_model(), 1);
+
+  ASSERT_EQ(tab_group_submenu.GetDisplayedGroupCount(), 1u);
+  deletion_dialog_controller()->SetPrefsPreventShowingDialogForTesting(
+      /*should_prevent_dialog*/ false);
+  tab_group_submenu.ExecuteExistingCommandForTesting(0);
+
+  // Check that the dialog is showing
+  ASSERT_TRUE(deletion_dialog_controller()->IsShowingDialog());
+  deletion_dialog_controller()->SimulateCancelButtonForTesting();
+  ASSERT_FALSE(deletion_dialog_controller()->IsShowingDialog());
+
+  // Check that nothing happened, the groups still exist and the closed saved
+  // tab group did not get any new tabs.
+  EXPECT_TRUE(tab_strip_model()->group_model()->ContainsTabGroup(blue));
+  EXPECT_EQ(tab_strip_model()->count(), 2);
+
+  std::optional<tab_groups::SavedTabGroup> group =
+      tab_group_sync_service()->GetGroup(closed_saved_group.saved_guid());
+
+  ASSERT_TRUE(group.has_value());
+  ASSERT_EQ(group->saved_tabs().size(), closed_saved_group.saved_tabs().size());
+}
+
+IN_PROC_BROWSER_TEST_F(ExistingTabGroupSubMenuModelClosedSavedGroupsTest,
+                       AddAllTabsToClosedSavedGroup) {
+  WaitForTabSyncServiceInitialization();
+
+  // Make a closed saved tab group.
+  tab_groups::SavedTabGroup closed_saved_group =
+      tab_groups::test::CreateTestSavedTabGroup(std::nullopt);
+  tab_group_sync_service()->AddGroup(closed_saved_group);
+
+  chrome::AddTabAt(browser(), GURL("chrome://newtab"), -1, true);
+
+  // Select all the tabs, and add it the closed saved group.
+  controller()->SelectTab(0, dummy_event);
+  controller()->ExtendSelectionTo(1);
+  ASSERT_TRUE(controller()->IsActiveTab(1));
+
+  std::unique_ptr<TabMenuModelDelegate> delegate =
+      CreateTabMenuModelDelegate(browser());
+  ExistingTabGroupSubMenuModel tab_group_submenu(nullptr, delegate.get(),
+                                                 tab_strip_model(), 1);
+
+  ASSERT_EQ(tab_group_submenu.GetDisplayedGroupCount(), 1u);
+  tab_group_submenu.ExecuteExistingCommandForTesting(0);
+
+  // After adding all tabs to the closed saved tab group, we should have a
+  // new tab that was added so the window does not close.
+  EXPECT_EQ(tab_strip_model()->count(), 1);
+
+  // Check the closed saved group was updated as well.
+  std::optional<tab_groups::SavedTabGroup> group =
+      tab_group_sync_service()->GetGroup(closed_saved_group.saved_guid());
+
+  ASSERT_TRUE(group.has_value());
+  ASSERT_EQ(group->saved_tabs().size(),
+            closed_saved_group.saved_tabs().size() + 2u);
+}
diff --git a/chrome/browser/ui/tabs/public/tab_features.h b/chrome/browser/ui/tabs/public/tab_features.h
index 81a7668a..a7b2596e 100644
--- a/chrome/browser/ui/tabs/public/tab_features.h
+++ b/chrome/browser/ui/tabs/public/tab_features.h
@@ -109,6 +109,7 @@
 
 namespace tab_groups {
 class SavedTabGroupWebContentsListener;
+class SavedTabGroupOnCloseHelper;
 }  // namespace tab_groups
 
 namespace page_actions {
@@ -205,6 +206,11 @@
     return saved_tab_group_web_contents_listener_.get();
   }
 
+  tab_groups::SavedTabGroupOnCloseHelper* saved_tab_group_on_close_helper()
+      const {
+    return saved_tab_group_on_close_helper_.get();
+  }
+
   TabDialogManager* tab_dialog_manager() { return tab_dialog_manager_.get(); }
 
   page_actions::PageActionController* page_action_controller() {
@@ -358,6 +364,9 @@
   std::unique_ptr<tab_groups::SavedTabGroupWebContentsListener>
       saved_tab_group_web_contents_listener_;
 
+  std::unique_ptr<tab_groups::SavedTabGroupOnCloseHelper>
+      saved_tab_group_on_close_helper_;
+
 #if BUILDFLAG(IS_CHROMEOS)
   // Manages the protocol handler picker dialog on ChromeOS. Must be destroyed
   // after the `tab_dialog_manager_`.
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/BUILD.gn b/chrome/browser/ui/tabs/saved_tab_groups/BUILD.gn
index 65445154..55a3179 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/BUILD.gn
+++ b/chrome/browser/ui/tabs/saved_tab_groups/BUILD.gn
@@ -18,6 +18,7 @@
     "saved_tab_group_controller.h",
     "saved_tab_group_metrics.h",
     "saved_tab_group_model_listener.h",
+    "saved_tab_group_on_close_helper.h",
     "saved_tab_group_pref_names.h",
     "saved_tab_group_utils.h",
     "saved_tab_group_web_contents_listener.h",
@@ -72,6 +73,7 @@
     "most_recent_shared_tab_update_store.cc",
     "saved_tab_group_metrics.cc",
     "saved_tab_group_model_listener.cc",
+    "saved_tab_group_on_close_helper.cc",
     "saved_tab_group_pref_names.cc",
     "saved_tab_group_utils.cc",
     "saved_tab_group_web_contents_listener.cc",
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_on_close_helper.cc b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_on_close_helper.cc
new file mode 100644
index 0000000..45196a28
--- /dev/null
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_on_close_helper.cc
@@ -0,0 +1,85 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_on_close_helper.h"
+
+#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
+#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
+#include "chrome/browser/ui/tab_ui_helper.h"
+#include "chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
+#include "components/saved_tab_groups/public/tab_group_sync_service.h"
+#include "components/tabs/public/tab_interface.h"
+#include "content/public/browser/web_contents.h"
+
+namespace tab_groups {
+
+SavedTabGroupOnCloseHelper::SavedTabGroupOnCloseHelper(
+    TabGroupSyncService* service,
+    tabs::TabInterface* tab)
+    : service_(service), tab_(tab), saved_tab_group_id_(std::nullopt) {}
+
+SavedTabGroupOnCloseHelper::~SavedTabGroupOnCloseHelper() = default;
+
+void SavedTabGroupOnCloseHelper::SetGroup(
+    const base::Uuid& closed_saved_group_id) {
+  CHECK(service_);
+  saved_tab_group_id_ = closed_saved_group_id;
+  Observe(tab_->GetContents());
+  tab_detach_subscription_ = tab_->RegisterWillDetach(base::BindRepeating(
+      &SavedTabGroupOnCloseHelper::OnTabClose, base::Unretained(this)));
+}
+
+void SavedTabGroupOnCloseHelper::UnsetGroup() {
+  saved_tab_group_id_ = std::nullopt;
+}
+
+bool SavedTabGroupOnCloseHelper::WillTryToAddToSavedGroupOnClose() {
+  return saved_tab_group_id_.has_value();
+}
+
+void SavedTabGroupOnCloseHelper::BeforeUnloadDialogCancelled() {
+  UnsetGroup();
+}
+
+void SavedTabGroupOnCloseHelper::OnTabClose(
+    tabs::TabInterface* tab,
+    tabs::TabInterface::DetachReason reason) {
+  if (tab_ != tab) {
+    return;
+  }
+
+  if (tab_->GetContents()->NeedToFireBeforeUnloadOrUnloadEvents()) {
+    return;
+  }
+
+  if (!saved_tab_group_id_.has_value()) {
+    return;
+  }
+
+  if (!service_) {
+    return;
+  }
+
+  // Add to the group on deletion
+  if (reason == tabs::TabInterface::DetachReason::kDelete) {
+    service_->AddUrl(saved_tab_group_id_.value(),
+                     tab_->GetContents()->GetTitle(),
+                     tab_->GetContents()->GetLastCommittedURL());
+  }
+
+  // Make a new tab if we are closing the last tab, to preserve the window.
+  // We do not want the window to close if the user selected all tabs and
+  // added them to a closed saved tab group.
+  TabStripModel* model = tab->GetBrowserWindowInterface()->GetTabStripModel();
+  CHECK(model);
+  if (model->count() == 1) {
+    model->delegate()->AddTabAt(GURL(), -1, true);
+  }
+
+  UnsetGroup();
+}
+
+}  // namespace tab_groups
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_on_close_helper.h b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_on_close_helper.h
new file mode 100644
index 0000000..7d96c41
--- /dev/null
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_on_close_helper.h
@@ -0,0 +1,55 @@
+
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_TABS_SAVED_TAB_GROUPS_SAVED_TAB_GROUP_ON_CLOSE_HELPER_H_
+#define CHROME_BROWSER_UI_TABS_SAVED_TAB_GROUPS_SAVED_TAB_GROUP_ON_CLOSE_HELPER_H_
+
+#include "base/callback_list.h"
+#include "base/memory/raw_ptr.h"
+#include "components/saved_tab_groups/public/saved_tab_group.h"
+#include "components/saved_tab_groups/public/types.h"
+#include "components/tabs/public/tab_interface.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace tab_groups {
+class TabGroupSyncService;
+
+// Listens for notifications about when a tab is being deleted. When it is
+// deleted, this class adds it to a specified closed saved tab group.
+class SavedTabGroupOnCloseHelper : public content::WebContentsObserver {
+ public:
+  SavedTabGroupOnCloseHelper(TabGroupSyncService* service,
+                             tabs::TabInterface* tab);
+
+  ~SavedTabGroupOnCloseHelper() override;
+
+  // Sets the closed saved group for which we will add |tab_| when
+  // |tab_| is deleted.
+  void SetGroup(const base::Uuid&);
+
+  void UnsetGroup();
+
+  // Testing function to check if we are set to add |tab_| to a closed
+  // saved group when |tab_| is deleted.
+  bool WillTryToAddToSavedGroupOnClose();
+
+  // contents::WebContentsObserver:
+  void BeforeUnloadDialogCancelled() override;
+
+  // Callback to tabs::TabInterface::RegisterWillDetach to get updates
+  // when |tab_| is closing.
+  void OnTabClose(tabs::TabInterface* tab,
+                  tabs::TabInterface::DetachReason reason);
+
+ private:
+  const raw_ptr<TabGroupSyncService> service_ = nullptr;
+  const raw_ptr<tabs::TabInterface> tab_ = nullptr;
+  std::optional<base::Uuid> saved_tab_group_id_ = std::nullopt;
+
+  base::CallbackListSubscription tab_detach_subscription_;
+};
+
+}  // namespace tab_groups
+#endif  // CHROME_BROWSER_UI_TABS_SAVED_TAB_GROUPS_SAVED_TAB_GROUP_ON_CLOSE_HELPER_H_
diff --git a/chrome/browser/ui/tabs/tab_features.cc b/chrome/browser/ui/tabs/tab_features.cc
index 7f414ed..53d57fe 100644
--- a/chrome/browser/ui/tabs/tab_features.cc
+++ b/chrome/browser/ui/tabs/tab_features.cc
@@ -56,6 +56,7 @@
 #include "chrome/browser/ui/tabs/public/tab_dialog_manager.h"
 #include "chrome/browser/ui/tabs/saved_tab_groups/collaboration_messaging_page_action_controller.h"
 #include "chrome/browser/ui/tabs/saved_tab_groups/collaboration_messaging_tab_data.h"
+#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_on_close_helper.h"
 #include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h"
 #include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_web_contents_listener.h"
 #include "chrome/browser/ui/tabs/tab_creation_metrics_controller.h"
@@ -313,6 +314,12 @@
       saved_tab_group_web_contents_listener_ =
           std::make_unique<tab_groups::SavedTabGroupWebContentsListener>(
               tab_group_sync_service, &tab);
+
+      if (features::IsTabGroupMenuMoreEntryPointsEnabled()) {
+        saved_tab_group_on_close_helper_ =
+            std::make_unique<tab_groups::SavedTabGroupOnCloseHelper>(
+                tab_group_sync_service, &tab);
+      }
     }
 
     if (tab_groups::SavedTabGroupUtils::SupportsSharedTabGroups()) {
diff --git a/chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h b/chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h
index f03f2d0..fb720ac 100644
--- a/chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h
+++ b/chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h
@@ -112,6 +112,7 @@
   // Gets the dialog state for tests. Allows for calling the callbacks without
   // going through views code.
   void SimulateOkButtonForTesting() { OnDialogOk(); }
+  void SimulateCancelButtonForTesting() { OnDialogCancel(); }
 
   void CreateDialogFromBrowser(BrowserWindowInterface* browser,
                                std::unique_ptr<ui::DialogModel> dialog_model);
diff --git a/chrome/browser/ui/tabs/tab_list_bridge.cc b/chrome/browser/ui/tabs/tab_list_bridge.cc
index 2e8b93b..08e10e37 100644
--- a/chrome/browser/ui/tabs/tab_list_bridge.cc
+++ b/chrome/browser/ui/tabs/tab_list_bridge.cc
@@ -4,12 +4,15 @@
 
 #include "chrome/browser/ui/tabs/tab_list_bridge.h"
 
+#include "base/check_op.h"
 #include "base/notimplemented.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
 #include "chrome/browser/ui/tabs/tab_enums.h"
+#include "chrome/browser/ui/tabs/tab_group_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
+#include "components/tabs/public/tab_group.h"
 #include "components/tabs/public/tab_interface.h"
 
 namespace {
@@ -177,6 +180,12 @@
 
   std::sort(tab_indices.begin(), tab_indices.end());
   if (group_id.has_value()) {
+    // No-op if the specified tab group does not exist.
+    // TODO(crbug.com/460650221): Add a quick test for this.
+    if (!tab_strip_->group_model()->GetTabGroup(*group_id)) {
+      return std::nullopt;
+    }
+
     tab_strip_->AddToExistingGroup(std::move(tab_indices), *group_id);
     return group_id;
   }
@@ -200,7 +209,56 @@
   tab_strip_->RemoveFromGroup(tab_indices);
 }
 
-void TabListBridge::MoveGroupTo(tab_groups::TabGroupId group_id, int index) {}
+void TabListBridge::MoveGroupTo(tab_groups::TabGroupId group_id, int index) {
+  // We don't know the group size because all we have here is a group id...
+  TabGroup* tab_group = tab_strip_->group_model()->GetTabGroup(group_id);
+  CHECK(tab_group) << "Tab group does not exist";
+
+  gfx::Range tabs = tab_group->ListTabs();
+  CHECK_GT(tabs.length(), 0u) << "Tab group is empty";
+
+  // Clamp the index to move for the first tab in the group to a valid index
+  // within the tab strip: the group should be after all pinned tabs and before
+  // the end of the tab strip.
+  size_t target_index =
+      std::clamp(static_cast<size_t>(index),
+                 static_cast<size_t>(tab_strip_->IndexOfFirstNonPinnedTab()),
+                 tab_strip_->count() - tabs.length());
+  // Return early if the index to move to is the same as the group's current
+  // index.
+  if (tabs.start() == target_index) {
+    return;
+  }
+
+  // Check that the index to move to is not in the middle of a tab group by
+  // checking whether the tab at `index_to_check` and the tab to its left is in
+  // the same tab group. Note that `GetTabGroupForTab` returns std::nullopt if
+  // the index is out of bounds.
+
+  // If the group will move to the right, compensate for the displacement of
+  // other tabs or tab groups to the right of the group before the move by
+  // adding the group's size to `target_index`. Basically, before MoveGroupTo is
+  // called:
+  // `index_to_check` points to the tab that will be to the right of the group
+  // after the move.
+  // `index_to_check` - 1 points to the tab that will be to the left of the
+  // group after the move.
+  const size_t index_to_check =
+      target_index > tabs.start() ? target_index + tabs.length() : target_index;
+
+  std::optional<tab_groups::TabGroupId> target_group =
+      tab_strip_->GetTabGroupForTab(index_to_check);
+  std::optional<tab_groups::TabGroupId> adjacent_group =
+      tab_strip_->GetTabGroupForTab(index_to_check - 1);
+
+  // TODO(crbug.com/460650221) As mentioned in the comment in TabListInterface,
+  // use the closest valid index if `index_to_check` falls in the middle of a
+  // group.
+  CHECK(!target_group.has_value() || target_group != adjacent_group)
+      << "Cannot move tab group into the middle of another group.";
+
+  tab_strip_->MoveGroupTo(group_id, target_index);
+}
 
 void TabListBridge::MoveTabToWindow(tabs::TabHandle tab,
                                     SessionID destination_window_id,
diff --git a/chrome/browser/ui/tabs/tab_list_bridge_browsertest.cc b/chrome/browser/ui/tabs/tab_list_bridge_browsertest.cc
index 18f3e1b..b5dd0e10 100644
--- a/chrome/browser/ui/tabs/tab_list_bridge_browsertest.cc
+++ b/chrome/browser/ui/tabs/tab_list_bridge_browsertest.cc
@@ -8,6 +8,7 @@
 
 #include "base/check_op.h"
 #include "base/memory/raw_ptr.h"
+#include "base/strings/stringprintf.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_group_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_test_utils.h"
@@ -560,3 +561,65 @@
   EXPECT_EQ("0 2 1",
             GetTabStripStateString(tab_strip_model, /*annotate_groups=*/true));
 }
+
+IN_PROC_BROWSER_TEST_F(TabListBridgeBrowserTest, MoveGroupTo) {
+  TabStripModel* tab_strip_model = browser()->tab_strip_model();
+  ASSERT_TRUE(tab_strip_model);
+
+  // Creates `num_tabs` tabs and sets their WebContents IDs to match their
+  // index.
+  auto setup_tabs = [this, tab_strip_model](size_t num_tabs) {
+    for (auto i = 0u; i < num_tabs; i++) {
+      auto disposition = i == 0u ? WindowOpenDisposition::CURRENT_TAB
+                                 : WindowOpenDisposition::NEW_BACKGROUND_TAB;
+      ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
+          browser(), GURL("about:blank"), disposition,
+          ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP))
+          << base::StringPrintf("Failed to open tab at index %u.", i);
+      SetID(tab_strip_model->GetWebContentsAt(i), i);
+    }
+  };
+
+  setup_tabs(10);
+  ASSERT_EQ("0 1 2 3 4 5 6 7 8 9",
+            GetTabStripStateString(tab_strip_model, /*annotate_groups=*/true));
+
+  TabListInterface* tab_list_interface = TabListInterface::From(browser());
+  ASSERT_TRUE(tab_list_interface);
+
+  auto first_group_id = tab_list_interface->AddTabsToGroup(
+      /*group_id=*/std::nullopt, {tab_list_interface->GetTab(0)->GetHandle(),
+                                  tab_list_interface->GetTab(1)->GetHandle(),
+                                  tab_list_interface->GetTab(2)->GetHandle()});
+  ASSERT_TRUE(first_group_id.has_value());
+
+  EXPECT_EQ("0g0 1g0 2g0 3 4 5 6 7 8 9",
+            GetTabStripStateString(tab_strip_model, /*annotate_groups=*/true));
+
+  // Move the tab group to the right by 1.
+  tab_list_interface->MoveGroupTo(*first_group_id, 1);
+  EXPECT_EQ("3 0g0 1g0 2g0 4 5 6 7 8 9",
+            GetTabStripStateString(tab_strip_model, /*annotate_groups=*/true));
+
+  // Group the tabs with WebContents IDs [6 7] and [8 9], then move
+  // `first_group_id` between them.
+  auto second_group_id = tab_list_interface->AddTabsToGroup(
+      /*group_id=*/std::nullopt, {tab_list_interface->GetTab(8)->GetHandle(),
+                                  tab_list_interface->GetTab(9)->GetHandle()});
+  auto third_group_id = tab_list_interface->AddTabsToGroup(
+      /*group_id=*/std::nullopt, {tab_list_interface->GetTab(6)->GetHandle(),
+                                  tab_list_interface->GetTab(7)->GetHandle()});
+  EXPECT_EQ("3 0g0 1g0 2g0 4 5 6g1 7g1 8g2 9g2",
+            GetTabStripStateString(tab_strip_model, /*annotate_groups=*/true));
+
+  tab_list_interface->MoveGroupTo(*first_group_id, 5);
+
+  // Note that `GetTabStripStateString` group annotation is meant to identify
+  // which tabs are in the same group, not the order of group IDs, hence the
+  // group ID checks below it.
+  EXPECT_EQ("3 4 5 6g0 7g0 0g1 1g1 2g1 8g2 9g2",
+            GetTabStripStateString(tab_strip_model, /*annotate_groups=*/true));
+  EXPECT_EQ(third_group_id, tab_strip_model->GetTabGroupForTab(3));
+  EXPECT_EQ(first_group_id, tab_strip_model->GetTabGroupForTab(5));
+  EXPECT_EQ(second_group_id, tab_strip_model->GetTabGroupForTab(8));
+}
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index 0b1f8be..e392490 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -81,6 +81,7 @@
 #include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_tab_helper.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/webui_url_constants.h"
 #include "components/commerce/core/commerce_utils.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
@@ -245,7 +246,8 @@
                              TabGroupModelFactory* group_model_factory)
     : delegate_(delegate),
       profile_(profile),
-      selection_model_(std::make_unique<ui::ListSelectionModel>()) {
+      selection_model_(std::make_unique<ui::ListSelectionModel>()),
+      focused_group_(std::nullopt) {
   DCHECK(delegate_);
 
   contents_data_ = std::make_unique<tabs::TabStripCollection>(false);
@@ -256,6 +258,56 @@
   scrubbing_metrics_.Init();
 }
 
+void TabStripModel::SetFocusedGroup(
+    std::optional<tab_groups::TabGroupId> group) {
+  CHECK(base::FeatureList::IsEnabled(features::kTabGroupsFocusing));
+
+  if (focused_group_ == group) {
+    return;
+  }
+
+  if (group.has_value() && group_model_ &&
+      group_model_->ContainsTabGroup(group.value())) {
+    const gfx::Range tabs_in_group =
+        group_model_->GetTabGroup(group.value())->ListTabs();
+    CHECK(!tabs_in_group.is_empty());
+
+    // Copy the previous selection model, but remove tabs not part of the
+    // tab_group in the list of selected tabs.
+    ui::ListSelectionModel new_selection_model = selection_model();
+    for (int index : selection_model_->selected_indices()) {
+      if (index < static_cast<int>(tabs_in_group.start()) ||
+          index >= static_cast<int>(tabs_in_group.end())) {
+        new_selection_model.RemoveIndexFromSelection(index);
+      }
+    }
+
+    // Update the anchor if its not within the tabgroup.
+    if (new_selection_model.anchor() <
+            static_cast<int>(tabs_in_group.start()) ||
+        new_selection_model.anchor() >= static_cast<int>(tabs_in_group.end())) {
+      new_selection_model.set_anchor(std::nullopt);
+    }
+
+    // Update the active tab if its not within the tabgroup.
+    if (GetTabGroupForTab(new_selection_model.active().value()) != group) {
+      new_selection_model.set_active(tabs_in_group.start());
+      new_selection_model.AddIndexToSelection(tabs_in_group.start());
+    }
+
+    DCHECK(!new_selection_model.empty());
+    SetSelection(std::move(new_selection_model),
+                 TabStripModelObserver::CHANGE_REASON_NONE,
+                 /*triggered_by_other_operation=*/false);
+  }
+
+  auto old_focused_group = focused_group_;
+  focused_group_ = group;
+  for (auto& observer : observers_) {
+    observer.OnTabGroupFocusChanged(focused_group_, old_focused_group);
+  }
+}
+
 TabStripModel::~TabStripModel() {
   for (auto& observer : observers_) {
     observer.ModelDestroyed(TabStripModelObserver::ModelPasskey(), this);
@@ -502,6 +554,10 @@
                          contents_data_->GetTabGroupCollection(group_id),
                          splits_in_group));
 
+  if (focused_group_ == group_id) {
+    SetFocusedGroup(std::nullopt);
+  }
+
   return std::make_unique<DetachedTabCollection>(
       base::WrapUnique(static_cast<tabs::TabGroupTabCollection*>(
           detached_collection.release())),
@@ -1198,11 +1254,19 @@
 
 void TabStripModel::CloseAllTabsInGroup(const tab_groups::TabGroupId& group) {
   ReentrancyCheck reentrancy_check(&reentrancy_guard_);
-
   if (!group_model_) {
     return;
   }
 
+  if (focused_group_ == group) {
+    SetFocusedGroup(std::nullopt);
+  }
+
+  CloseAllTabsInGroupImpl(group);
+}
+
+void TabStripModel::CloseAllTabsInGroupImpl(
+    const tab_groups::TabGroupId& group) {
   delegate_->WillCloseGroup(group);
 
   for (TabStripModelObserver& observer : observers_) {
@@ -2052,6 +2116,15 @@
                   SplitTabChange::SplitTabRemoveReason::kSplitTabRemoved);
 }
 
+// Returns the ID of the group that is focused. If no group is focused,
+// returns nullopt.
+std::optional<tab_groups::TabGroupId> TabStripModel::GetFocusedGroup() const {
+  if (!base::FeatureList::IsEnabled(features::kTabGroupsFocusing)) {
+    return std::nullopt;
+  }
+  return focused_group_;
+}
+
 bool TabStripModel::IsReadLaterSupportedForAny(
     const std::vector<int>& indices) {
   if (!delegate_->SupportsReadLater()) {
@@ -4516,6 +4589,9 @@
 
     // If the group model must be deleted, then do that at this point.
     if (tab_group->IsEmpty()) {
+      if (focused_group_ == initial_group) {
+        SetFocusedGroup(std::nullopt);
+      }
       NotifyTabGroupClosed(initial_group.value());
       group_model_->RemoveTabGroup(initial_group.value(),
                                    base::PassKey<TabStripModel>());
diff --git a/chrome/browser/ui/tabs/tab_strip_model.h b/chrome/browser/ui/tabs/tab_strip_model.h
index 8930a0b..96dd408 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.h
+++ b/chrome/browser/ui/tabs/tab_strip_model.h
@@ -16,6 +16,7 @@
 #include <vector>
 
 #include "base/containers/span.h"
+#include "base/feature_list.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -27,6 +28,7 @@
 #include "chrome/browser/ui/tabs/tab_strip_scrubbing_metrics.h"
 #include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/common/buildflags.h"
+#include "chrome/common/chrome_features.h"
 #include "components/sessions/core/session_id.h"
 #include "components/tab_groups/tab_group_id.h"
 #include "components/tab_groups/tab_group_visual_data.h"
@@ -460,7 +462,8 @@
   // notifications this method causes.
   void CloseAllTabs();
 
-  // Close all tabs in the given |group| at once.
+  // Close all tabs in the given |group| at once, but sets the focus state
+  // first.
   void CloseAllTabsInGroup(const tab_groups::TabGroupId& group);
 
   // Returns true if there are any WebContentses that are currently loading
@@ -696,6 +699,13 @@
 
   bool SupportsTabGroups() const { return group_model_.get() != nullptr; }
 
+  // Returns the ID of the group that is focused. If no group is focused,
+  // returns nullopt.
+  std::optional<tab_groups::TabGroupId> GetFocusedGroup() const;
+
+  // Sets the group to be focused.
+  void SetFocusedGroup(std::optional<tab_groups::TabGroupId> group);
+
   // Returns true if one or more of the tabs pointed to by |indices| are
   // supported by read later.
   bool IsReadLaterSupportedForAny(const std::vector<int>& indices);
@@ -1147,6 +1157,9 @@
       TabStripModelObserver::ChangeReason reason,
       bool triggered_by_other_operation);
 
+  // Close all tabs in the given |group| at once.
+  void CloseAllTabsInGroupImpl(const tab_groups::TabGroupId& group);
+
   // Direction of relative tab movements or selections. kNext indicates moving
   // forward (positive increment) in the tab strip. kPrevious indicates
   // backward (negative increment).
@@ -1409,6 +1422,9 @@
   // Tracks whether a modal UI is showing.
   bool showing_modal_ui_ = false;
 
+  // The focused group. If no group is focused, this is nullopt.
+  std::optional<tab_groups::TabGroupId> focused_group_;
+
   base::WeakPtrFactory<TabStripModel> weak_factory_{this};
 };
 
diff --git a/chrome/browser/ui/tabs/tab_strip_model_observer.cc b/chrome/browser/ui/tabs/tab_strip_model_observer.cc
index e748e82..742f998 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_observer.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model_observer.cc
@@ -355,6 +355,10 @@
 
 void TabStripModelObserver::OnTabGroupChanged(const TabGroupChange& change) {}
 
+void TabStripModelObserver::OnTabGroupFocusChanged(
+    std::optional<tab_groups::TabGroupId> new_focused_group_id,
+    std::optional<tab_groups::TabGroupId> old_focused_group_id) {}
+
 void TabStripModelObserver::OnTabGroupAdded(
     const tab_groups::TabGroupId& group_id) {}
 
diff --git a/chrome/browser/ui/tabs/tab_strip_model_observer.h b/chrome/browser/ui/tabs/tab_strip_model_observer.h
index 62825b9..2cda946 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_observer.h
+++ b/chrome/browser/ui/tabs/tab_strip_model_observer.h
@@ -548,6 +548,12 @@
   // independent of the tabstrip model and do not affect any tab state.
   virtual void OnTabGroupChanged(const TabGroupChange& change);
 
+  // Called when the "GroupFocused" state changes. This will happen before the
+  // group is fully destroyed.
+  virtual void OnTabGroupFocusChanged(
+      std::optional<tab_groups::TabGroupId> new_focused_group_id,
+      std::optional<tab_groups::TabGroupId> old_focused_group_id);
+
   // Notfies us when a Tab Group is added to the Tab Group Model.
   virtual void OnTabGroupAdded(const tab_groups::TabGroupId& group_id);
 
diff --git a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
index 2346f30..5755c62b 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
@@ -43,6 +43,7 @@
 #include "chrome/browser/ui/tabs/tab_utils.h"
 #include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h"
 #include "chrome/browser/ui/ui_features.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/commerce/core/commerce_utils.h"
@@ -368,6 +369,12 @@
     }
   }
 
+  MOCK_METHOD(void,
+              OnTabGroupFocusChanged,
+              (std::optional<tab_groups::TabGroupId> new_focused_group_id,
+               std::optional<tab_groups::TabGroupId> old_focused_group_id),
+              (override));
+
   ObservedSelectionChange GetLatestSelectionChange() {
     return latest_selection_change_;
   }
@@ -446,7 +453,8 @@
 
   void SetUp() override {
     std::vector<base::test::FeatureRef> enabled_features = {
-        features::kSideBySide, features::kSideBySideSessionRestore};
+        features::kSideBySide, features::kSideBySideSessionRestore,
+        features::kTabGroupsFocusing};
     std::vector<base::test::FeatureRef> disabled_features;
     if (GetParam()) {
       enabled_features.push_back(tabs::kTabSelectionByPointer);
@@ -1319,6 +1327,305 @@
   EXPECT_TRUE(tabstrip()->empty());
 }
 
+TEST_P(TabStripModelTest, FocusMode) {
+  ASSERT_TRUE(tabstrip()->SupportsTabGroups());
+
+  // Initially, no group should be focused.
+  EXPECT_FALSE(tabstrip()->GetFocusedGroup().has_value());
+
+  // Add some tabs and create a group.
+  tabstrip()->AppendWebContents(CreateWebContents(), true);
+  tabstrip()->AppendWebContents(CreateWebContents(), false);
+  tabstrip()->AppendWebContents(CreateWebContents(), false);
+  tab_groups::TabGroupId group_id = tabstrip()->AddToNewGroup({0, 1});
+
+  // Focus the group.
+  tabstrip()->SetFocusedGroup(group_id);
+  EXPECT_EQ(group_id, tabstrip()->GetFocusedGroup());
+
+  // Setting the same group again should not change anything.
+  tabstrip()->SetFocusedGroup(group_id);
+  EXPECT_EQ(group_id, tabstrip()->GetFocusedGroup());
+
+  // Unfocus the group.
+  tabstrip()->SetFocusedGroup(std::nullopt);
+  EXPECT_FALSE(tabstrip()->GetFocusedGroup().has_value());
+}
+
+TEST_P(TabStripModelTest, ClosingFocusedGroupUnsetsFocus) {
+  TestTabStripModelDelegate delegate;
+  TabStripModel model(&delegate, profile());
+  ASSERT_TRUE(model.empty());
+
+  model.AppendWebContents(CreateWebContents(), true);
+  model.AppendWebContents(CreateWebContents(), true);
+
+  const tab_groups::TabGroupId group = model.AddToNewGroup({0, 1});
+  model.SetFocusedGroup(group);
+  EXPECT_EQ(model.GetFocusedGroup(), group);
+
+  model.CloseAllTabsInGroup(group);
+  EXPECT_EQ(model.GetFocusedGroup(), std::nullopt);
+}
+
+TEST_P(TabStripModelTest, UngroupingFocusedGroupUnsetsFocus) {
+  TestTabStripModelDelegate delegate;
+  TabStripModel model(&delegate, profile());
+  ASSERT_TRUE(model.empty());
+
+  model.AppendWebContents(CreateWebContents(), true);
+  model.AppendWebContents(CreateWebContents(), true);
+
+  const tab_groups::TabGroupId group = model.AddToNewGroup({0, 1});
+  model.SetFocusedGroup(group);
+  EXPECT_EQ(model.GetFocusedGroup(), group);
+
+  model.RemoveFromGroup({0, 1});
+  EXPECT_EQ(model.GetFocusedGroup(), std::nullopt);
+}
+
+TEST_P(TabStripModelTest, RemovingLastTabOfFocusedGroupUnsetsFocus) {
+  TestTabStripModelDelegate delegate;
+  TabStripModel model(&delegate, profile());
+  ASSERT_TRUE(model.empty());
+
+  model.AppendWebContents(CreateWebContents(), true);
+  model.AppendWebContents(CreateWebContents(), true);
+
+  const tab_groups::TabGroupId group = model.AddToNewGroup({0, 1});
+  model.SetFocusedGroup(group);
+  EXPECT_EQ(model.GetFocusedGroup(), group);
+
+  model.RemoveFromGroup({1});
+  EXPECT_EQ(model.GetFocusedGroup(), group);
+
+  model.RemoveFromGroup({0});
+  EXPECT_EQ(model.GetFocusedGroup(), std::nullopt);
+}
+
+TEST_P(TabStripModelTest, FocusGroupActivatesTabs) {
+  ASSERT_TRUE(tabstrip()->SupportsTabGroups());
+
+  // Add 4 tabs.
+  tabstrip()->AppendWebContents(CreateWebContents(), true);
+  tabstrip()->AppendWebContents(CreateWebContents(), false);
+  tabstrip()->AppendWebContents(CreateWebContents(), false);
+  tabstrip()->AppendWebContents(CreateWebContents(), false);
+  EXPECT_EQ(4, tabstrip()->count());
+  EXPECT_EQ(0, tabstrip()->active_index());
+
+  // Group tabs at index 1 and 2.
+  tab_groups::TabGroupId group_id = tabstrip()->AddToNewGroup({1, 2});
+
+  // Initially, only active tab is selected.
+  EXPECT_TRUE(tabstrip()->IsTabSelected(0));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(1));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(2));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(3));
+  EXPECT_EQ(1u, tabstrip()->selection_model().size());
+
+  // Focus the group.
+  tabstrip()->SetFocusedGroup(group_id);
+
+  // Now tabs 1 should be selected.
+  EXPECT_FALSE(tabstrip()->IsTabSelected(0));
+  EXPECT_TRUE(tabstrip()->IsTabSelected(1));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(2));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(3));
+  EXPECT_EQ(1u, tabstrip()->selection_model().size());
+
+  // The active tab should be in the group. Since the active tab was 0 (outside
+  // the group), it should have moved to the first tab of the group (index 1).
+  EXPECT_EQ(1, tabstrip()->active_index());
+
+  // Activate tab 2, which is in the same focused group.
+  tabstrip()->ActivateTabAt(
+      2, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
+
+  // The active tab should change.
+  EXPECT_FALSE(tabstrip()->IsTabSelected(0));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(1));
+  EXPECT_TRUE(tabstrip()->IsTabSelected(2));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(3));
+  EXPECT_EQ(1u, tabstrip()->selection_model().size());
+  EXPECT_EQ(2, tabstrip()->active_index());
+
+  // Now, focus another group.
+  tab_groups::TabGroupId group_id2 = tabstrip()->AddToNewGroup({3});
+  tabstrip()->SetFocusedGroup(group_id2);
+
+  // Now only tab 3 should be selected.
+  EXPECT_FALSE(tabstrip()->IsTabSelected(0));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(1));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(2));
+  EXPECT_TRUE(tabstrip()->IsTabSelected(3));
+  EXPECT_EQ(1u, tabstrip()->selection_model().size());
+  EXPECT_EQ(3, tabstrip()->active_index());
+
+  // Selection remains the same if the Focus is cleared.
+  tabstrip()->SetFocusedGroup(std::nullopt);
+  EXPECT_FALSE(tabstrip()->IsTabSelected(0));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(1));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(2));
+  EXPECT_TRUE(tabstrip()->IsTabSelected(3));
+  EXPECT_EQ(1u, tabstrip()->selection_model().size());
+  EXPECT_EQ(3, tabstrip()->active_index());
+}
+
+TEST_P(TabStripModelTest, FocusGroupClampsSelection) {
+  // Add 4 tabs.
+  tabstrip()->AppendWebContents(CreateWebContents(), true);
+  tabstrip()->AppendWebContents(CreateWebContents(), false);
+  tabstrip()->AppendWebContents(CreateWebContents(), false);
+  tabstrip()->AppendWebContents(CreateWebContents(), false);
+
+  // group the last 2 tabs.
+  tab_groups::TabGroupId group_id = tabstrip()->AddToNewGroup({2, 3});
+
+  // Select all of the tabs
+  ui::ListSelectionModel selection_model;
+  selection_model.set_anchor(std::nullopt);
+  selection_model.set_active(0);
+  selection_model.AddIndexToSelection(0);
+  selection_model.AddIndexToSelection(1);
+  selection_model.AddIndexToSelection(2);
+  selection_model.AddIndexToSelection(3);
+  tabstrip()->SetSelectionFromModel(selection_model);
+
+  EXPECT_TRUE(tabstrip()->IsTabSelected(0));
+  EXPECT_TRUE(tabstrip()->IsTabSelected(1));
+  EXPECT_TRUE(tabstrip()->IsTabSelected(2));
+  EXPECT_TRUE(tabstrip()->IsTabSelected(3));
+  EXPECT_EQ(4u, tabstrip()->selection_model().size());
+  EXPECT_EQ(0, tabstrip()->active_index());
+
+  // Now only tabs in the group should be selected.
+  tabstrip()->SetFocusedGroup(group_id);
+
+  EXPECT_FALSE(tabstrip()->IsTabSelected(0));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(1));
+  EXPECT_TRUE(tabstrip()->IsTabSelected(2));
+  EXPECT_TRUE(tabstrip()->IsTabSelected(3));
+  EXPECT_EQ(2u, tabstrip()->selection_model().size());
+  EXPECT_EQ(2, tabstrip()->active_index());
+
+  // Selection remains the same if the Focus is cleared.
+  tabstrip()->SetFocusedGroup(std::nullopt);
+  EXPECT_FALSE(tabstrip()->IsTabSelected(0));
+  EXPECT_FALSE(tabstrip()->IsTabSelected(1));
+  EXPECT_TRUE(tabstrip()->IsTabSelected(2));
+  EXPECT_TRUE(tabstrip()->IsTabSelected(3));
+  EXPECT_EQ(2u, tabstrip()->selection_model().size());
+  EXPECT_EQ(2, tabstrip()->active_index());
+}
+
+TEST_P(TabStripModelTest, OnTabGroupFocusChangedObserver) {
+  ASSERT_TRUE(tabstrip()->SupportsTabGroups());
+
+  // Add some tabs and create a group.
+  tabstrip()->AppendWebContents(CreateWebContents(), true);
+  tabstrip()->AppendWebContents(CreateWebContents(), false);
+  tabstrip()->AppendWebContents(CreateWebContents(), false);
+  tab_groups::TabGroupId group_id = tabstrip()->AddToNewGroup({0, 1});
+
+  // Mock observer to track calls to OnTabGroupFocusChanged.
+  testing::StrictMock<MockTabStripModelObserver> mock_observer;
+  tabstrip()->AddObserver(&mock_observer);
+
+  // Expect a call when the group is focused.
+  EXPECT_CALL(mock_observer, OnTabGroupFocusChanged(testing::Optional(group_id),
+                                                    testing::Eq(std::nullopt)));
+  tabstrip()->SetFocusedGroup(group_id);
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  // Expect no call when the same group is focused again.
+  EXPECT_CALL(mock_observer, OnTabGroupFocusChanged(testing::_, testing::_))
+      .Times(0);
+  tabstrip()->SetFocusedGroup(group_id);
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  // Expect a call when the group is unfocused.
+  EXPECT_CALL(mock_observer,
+              OnTabGroupFocusChanged(testing::Eq(std::nullopt),
+                                     testing::Optional(group_id)));
+  tabstrip()->SetFocusedGroup(std::nullopt);
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  tabstrip()->RemoveObserver(&mock_observer);
+}
+
+TEST_P(TabStripModelTest, ClosingFocusedGroupUnsetsFocusAndNotifies) {
+  ASSERT_TRUE(tabstrip()->SupportsTabGroups());
+  tabstrip()->AppendWebContents(CreateWebContents(), true);
+  tabstrip()->AppendWebContents(CreateWebContents(), true);
+
+  const tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0, 1});
+
+  testing::StrictMock<MockTabStripModelObserver> mock_observer;
+  tabstrip()->AddObserver(&mock_observer);
+
+  EXPECT_CALL(mock_observer, OnTabGroupFocusChanged(testing::Optional(group),
+                                                    testing::Eq(std::nullopt)));
+  tabstrip()->SetFocusedGroup(group);
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  EXPECT_CALL(mock_observer, OnTabGroupFocusChanged(testing::Eq(std::nullopt),
+                                                    testing::Optional(group)));
+  tabstrip()->CloseAllTabsInGroup(group);
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  EXPECT_EQ(tabstrip()->GetFocusedGroup(), std::nullopt);
+  tabstrip()->RemoveObserver(&mock_observer);
+}
+
+TEST_P(TabStripModelTest, UngroupingFocusedGroupUnsetsFocusAndNotifies) {
+  ASSERT_TRUE(tabstrip()->SupportsTabGroups());
+  tabstrip()->AppendWebContents(CreateWebContents(), true);
+  tabstrip()->AppendWebContents(CreateWebContents(), true);
+
+  const tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0, 1});
+
+  testing::StrictMock<MockTabStripModelObserver> mock_observer;
+  tabstrip()->AddObserver(&mock_observer);
+
+  EXPECT_CALL(mock_observer, OnTabGroupFocusChanged(testing::Optional(group),
+                                                    testing::Eq(std::nullopt)));
+  tabstrip()->SetFocusedGroup(group);
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  EXPECT_CALL(mock_observer, OnTabGroupFocusChanged(testing::Eq(std::nullopt),
+                                                    testing::Optional(group)));
+  tabstrip()->RemoveFromGroup({0, 1});
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  EXPECT_EQ(tabstrip()->GetFocusedGroup(), std::nullopt);
+  tabstrip()->RemoveObserver(&mock_observer);
+}
+
+TEST_P(TabStripModelTest, ClosingLastTabOfFocusedGroupUnsetsFocusAndNotifies) {
+  ASSERT_TRUE(tabstrip()->SupportsTabGroups());
+  tabstrip()->AppendWebContents(CreateWebContents(), true);
+
+  const tab_groups::TabGroupId group = tabstrip()->AddToNewGroup({0});
+
+  testing::StrictMock<MockTabStripModelObserver> mock_observer;
+  tabstrip()->AddObserver(&mock_observer);
+
+  EXPECT_CALL(mock_observer, OnTabGroupFocusChanged(testing::Optional(group),
+                                                    testing::Eq(std::nullopt)));
+  tabstrip()->SetFocusedGroup(group);
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  EXPECT_CALL(mock_observer, OnTabGroupFocusChanged(testing::Eq(std::nullopt),
+                                                    testing::Optional(group)));
+  tabstrip()->CloseWebContentsAt(0, TabCloseTypes::CLOSE_NONE);
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  EXPECT_EQ(tabstrip()->GetFocusedGroup(), std::nullopt);
+  tabstrip()->RemoveObserver(&mock_observer);
+}
+
 // This test constructs a tabstrip, and then simulates loading several tabs in
 // the background from link clicks on the first tab. Then it simulates opening
 // a new tab from the first tab in the foreground via a link click, verifies
@@ -1943,6 +2250,46 @@
   EXPECT_TRUE(tabstrip()->empty());
 }
 
+TEST_P(TabStripModelTest, SetFocusedGroupActivatesTab) {
+  ASSERT_TRUE(tabstrip()->SupportsTabGroups());
+
+  // Add some tabs and create a group.
+  tabstrip()->AppendWebContents(CreateWebContents(), true);
+  tabstrip()->AppendWebContents(CreateWebContents(), false);
+  tabstrip()->AppendWebContents(CreateWebContents(), false);
+  tab_groups::TabGroupId group_id = tabstrip()->AddToNewGroup({1, 2});
+
+  // Make sure the active tab is outside the group.
+  tabstrip()->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
+  EXPECT_EQ(0, tabstrip()->active_index());
+
+  // Focus the group.
+  tabstrip()->SetFocusedGroup(group_id);
+  EXPECT_EQ(group_id, tabstrip()->GetFocusedGroup());
+
+  // The active tab should now be in the group.
+  EXPECT_EQ(group_id,
+            tabstrip()->GetTabGroupForTab(tabstrip()->active_index()));
+}
+
+TEST_P(TabStripModelTest, DetachingFocusedGroupUnsetsFocus) {
+  TestTabStripModelDelegate delegate;
+  TabStripModel model(&delegate, profile());
+  ASSERT_TRUE(model.empty());
+
+  model.AppendWebContents(CreateWebContents(), true);
+  model.AppendWebContents(CreateWebContents(), true);
+
+  const tab_groups::TabGroupId group = model.AddToNewGroup({0, 1});
+  model.SetFocusedGroup(group);
+  EXPECT_EQ(model.GetFocusedGroup(), group);
+
+  model.DetachTabGroupForInsertion(group);
+  EXPECT_EQ(model.GetFocusedGroup(), std::nullopt);
+}
+
 TEST_P(TabStripModelTest, SplitTabPinning) {
   for (bool split_is_selected : {true, false}) {
     for (bool use_left_tab : {true, false}) {
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index d0087222..96c08a5 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -304,7 +304,7 @@
 #endif
 );
 
-BASE_FEATURE(kSideBySideKeyboardShortcut, base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kSideBySideKeyboardShortcut, base::FEATURE_ENABLED_BY_DEFAULT);
 
 bool IsSideBySideKeyboardShortcutEnabled() {
   return base::FeatureList::IsEnabled(features::kSideBySide) &&
@@ -534,7 +534,7 @@
 
 BASE_FEATURE(kInlineFullscreenPerfExperiment, base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kPageActionsMigration, base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kPageActionsMigration, base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationEnableAll,
@@ -546,67 +546,67 @@
                    kPageActionsMigrationLensOverlay,
                    &kPageActionsMigration,
                    "lens_overlay",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationMemorySaver,
                    &kPageActionsMigration,
                    "memory_saver",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationTranslate,
                    &kPageActionsMigration,
                    "translate",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationIntentPicker,
                    &kPageActionsMigration,
                    "intent_picker",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationZoom,
                    &kPageActionsMigration,
                    "zoom",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationOfferNotification,
                    &kPageActionsMigration,
                    "offer_notification",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationFileSystemAccess,
                    &kPageActionsMigration,
                    "file_system_access",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationPwaInstall,
                    &kPageActionsMigration,
                    "pwa_install",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationPriceInsights,
                    &kPageActionsMigration,
                    "price_insights",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationDiscounts,
                    &kPageActionsMigration,
                    "discounts",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationManagePasswords,
                    &kPageActionsMigration,
                    "manage_passwords",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationCookieControls,
@@ -618,25 +618,25 @@
                    kPageActionsMigrationAutofillAddress,
                    &kPageActionsMigration,
                    "autofill_address",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationFind,
                    &kPageActionsMigration,
                    "find",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationCollaborationMessaging,
                    &kPageActionsMigration,
                    "collaboration_messaging",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationPriceTracking,
                    &kPageActionsMigration,
                    "price_tracking",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationAutofillMandatoryReauth,
@@ -648,7 +648,7 @@
                    kPageActionsMigrationClickToCall,
                    &kPageActionsMigration,
                    "click_to_call",
-                   false);
+                   true);
 
 BASE_FEATURE_PARAM(bool,
                    kPageActionsMigrationSharingHub,
@@ -789,4 +789,6 @@
 
 BASE_FEATURE(kWhatsNewDesktopRefresh, base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kTabGroupsFocusing, base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace features
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h
index 337335c..419ceec 100644
--- a/chrome/browser/ui/ui_features.h
+++ b/chrome/browser/ui/ui_features.h
@@ -476,6 +476,8 @@
 // Controls whether the updated What's New page is enabled.
 BASE_DECLARE_FEATURE(kWhatsNewDesktopRefresh);
 
+BASE_DECLARE_FEATURE(kTabGroupsFocusing);
+
 }  // namespace features
 
 #endif  // CHROME_BROWSER_UI_UI_FEATURES_H_
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_editor_view_unittest.cc b/chrome/browser/ui/views/bookmarks/bookmark_editor_view_unittest.cc
index 43fbdc75..373739d 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_editor_view_unittest.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_editor_view_unittest.cc
@@ -367,7 +367,6 @@
   // F1 should have one child, F11
   const BookmarkEditorView::EditorNode* F1 =
       local_bookmark_bar->children()[0].get();
-  account_bookmark_bar_editor_node()->children()[0].get();
   ASSERT_EQ(1u, F1->children().size());
   EXPECT_EQ(u"F11", F1->children()[0]->GetTitle());
   // Local other node should have one child (OF1).
diff --git a/chrome/browser/ui/views/commerce/price_tracking_icon_view_integration_test.cc b/chrome/browser/ui/views/commerce/price_tracking_icon_view_integration_test.cc
index 7bb33d23..c410931 100644
--- a/chrome/browser/ui/views/commerce/price_tracking_icon_view_integration_test.cc
+++ b/chrome/browser/ui/views/commerce/price_tracking_icon_view_integration_test.cc
@@ -14,11 +14,13 @@
 #include "chrome/browser/ui/browser_element_identifiers.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/commerce/mock_commerce_ui_tab_helper.h"
+#include "chrome/browser/ui/page_action/page_action_icon_type.h"
 #include "chrome/browser/ui/tabs/public/tab_features.h"
 #include "chrome/browser/ui/views/commerce/price_tracking_bubble_dialog_view.h"
 #include "chrome/browser/ui/views/commerce/price_tracking_icon_view.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/test_with_browser_view.h"
+#include "chrome/browser/ui/views/location_bar/icon_label_bubble_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "components/bookmarks/browser/bookmark_utils.h"
 #include "components/bookmarks/test/bookmark_test_helpers.h"
@@ -59,6 +61,11 @@
   void SetUp() override {
     test_features_.InitAndEnableFeature(commerce::kShoppingList);
     TestWithBrowserView::SetUp();
+    if (IsPageActionMigrated(PageActionIconType::kPriceTracking)) {
+      GTEST_SKIP() << "The price tracking page action is removed for the page "
+                      "actions framework migration.";
+    }
+
     AddTab(browser(), GURL(kNonTrackableUrl));
     mock_tab_helper_ =
         static_cast<MockCommerceUiTabHelper*>(browser()
diff --git a/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate.h b/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate.h
index 2f74f97..64dce1b 100644
--- a/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate.h
+++ b/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate.h
@@ -27,6 +27,7 @@
   virtual ~BrowserViewLayoutDelegate() = default;
 
   virtual bool ShouldDrawTabStrip() const = 0;
+  virtual bool ShouldUseTouchableTabstrip() const = 0;
   virtual bool ShouldDrawVerticalTabStrip() const = 0;
   virtual bool ShouldDrawWebAppFrameToolbar() const = 0;
   virtual bool GetBorderlessModeEnabled() const = 0;
diff --git a/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.cc b/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.cc
index 66adac91..6ac93854 100644
--- a/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.cc
+++ b/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.cc
@@ -17,9 +17,14 @@
 #include "chrome/browser/ui/views/infobars/infobar_container_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h"
+#include "chrome/common/buildflags.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/view.h"
 
+#if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
+#include "chrome/browser/ui/views/frame/webui_tab_strip_container_view.h"
+#endif
+
 BrowserViewLayoutDelegateImpl::BrowserViewLayoutDelegateImpl(
     BrowserView& browser_view)
     : browser_view_(browser_view) {}
@@ -29,6 +34,16 @@
   return browser_view_->ShouldDrawTabStrip();
 }
 
+bool BrowserViewLayoutDelegateImpl::ShouldUseTouchableTabstrip() const {
+#if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
+  return WebUITabStripContainerView::UseTouchableTabStrip(
+             browser_view_->browser()) &&
+         browser_view_->GetSupportsTabStrip();
+#else
+  return false;
+#endif
+}
+
 bool BrowserViewLayoutDelegateImpl::ShouldDrawVerticalTabStrip() const {
   return ShouldDrawTabStrip() && tabs::IsVerticalTabsFeatureEnabled() &&
          browser_view_->browser()
diff --git a/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.h b/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.h
index 878896a..4308ae9 100644
--- a/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.h
+++ b/chrome/browser/ui/views/frame/layout/browser_view_layout_delegate_impl.h
@@ -22,6 +22,7 @@
   ~BrowserViewLayoutDelegateImpl() override;
 
   bool ShouldDrawTabStrip() const override;
+  bool ShouldUseTouchableTabstrip() const override;
   bool ShouldDrawVerticalTabStrip() const override;
   bool ShouldDrawWebAppFrameToolbar() const override;
   bool GetBorderlessModeEnabled() const override;
diff --git a/chrome/browser/ui/views/frame/layout/browser_view_layout_unittest.cc b/chrome/browser/ui/views/frame/layout/browser_view_layout_unittest.cc
index 581d996..4359af88 100644
--- a/chrome/browser/ui/views/frame/layout/browser_view_layout_unittest.cc
+++ b/chrome/browser/ui/views/frame/layout/browser_view_layout_unittest.cc
@@ -63,6 +63,7 @@
 
   // BrowserViewLayout::Delegate overrides:
   bool ShouldDrawTabStrip() const override { return should_draw_tab_strip_; }
+  bool ShouldUseTouchableTabstrip() const override { return false; }
   bool ShouldDrawVerticalTabStrip() const override { return false; }
   bool GetBorderlessModeEnabled() const override { return false; }
   bool ShouldDrawWebAppFrameToolbar() const override { return false; }
diff --git a/chrome/browser/ui/views/frame/layout/browser_view_tabbed_layout_impl.cc b/chrome/browser/ui/views/frame/layout/browser_view_tabbed_layout_impl.cc
index 5734efd..99715b8 100644
--- a/chrome/browser/ui/views/frame/layout/browser_view_tabbed_layout_impl.cc
+++ b/chrome/browser/ui/views/frame/layout/browser_view_tabbed_layout_impl.cc
@@ -8,6 +8,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/numerics/safe_conversions.h"
 #include "base/trace_event/common/trace_event_common.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
@@ -99,8 +100,11 @@
       return std::make_pair(result, gfx::Size());
     }
     case TabStripType::kWebUi:
+      // WebUI tabstrip is lazily-created.
       return std::make_pair(gfx::Size(),
-                            views().webui_tab_strip->GetMinimumSize());
+                            views().webui_tab_strip
+                                ? views().webui_tab_strip->GetMinimumSize()
+                                : gfx::Size());
     case TabStripType::kNone:
       return std::make_pair(gfx::Size(), gfx::Size());
   }
@@ -134,7 +138,7 @@
 
 BrowserViewTabbedLayoutImpl::TabStripType
 BrowserViewTabbedLayoutImpl::GetTabStripType() const {
-  if (views().webui_tab_strip && views().webui_tab_strip->GetVisible()) {
+  if (delegate().ShouldUseTouchableTabstrip()) {
     return TabStripType::kWebUi;
   }
   if (delegate().ShouldDrawVerticalTabStrip()) {
@@ -201,10 +205,20 @@
   bool needs_exclusion = true;
   const TabStripType tab_strip_type = GetTabStripType();
 
+  if (tab_strip_type == TabStripType::kWebUi) {
+    // When the WebUI tab strip is present, it does not paint over the caption
+    // buttons or other exclusion areas.
+    params.SetTop(base::ClampCeil(
+        std::max(params.leading_exclusion.ContentWithPadding().height(),
+                 params.trailing_exclusion.ContentWithPadding().height())));
+    needs_exclusion = false;
+  }
+
   // Lay out WebUI tabstrip if visible.
   if (IsParentedTo(views().webui_tab_strip, views().browser_view)) {
     const int width = params.visual_client_area.width();
-    const int height = tab_strip_type == TabStripType::kWebUi
+    const int height = tab_strip_type == TabStripType::kWebUi &&
+                               views().webui_tab_strip->GetVisible()
                            ? views().webui_tab_strip->GetHeightForWidth(width)
                            : 0;
     layout.AddChild(views().webui_tab_strip,
@@ -545,7 +559,8 @@
   // immersive mode), ensure it is laid out here.
   if (IsParentedTo(views().webui_tab_strip, views().top_container)) {
     const int width = params.visual_client_area.width();
-    const int height = tab_strip_type == TabStripType::kWebUi
+    const int height = tab_strip_type == TabStripType::kWebUi &&
+                               views().webui_tab_strip->GetVisible()
                            ? views().webui_tab_strip->GetHeightForWidth(width)
                            : 0;
     layout.AddChild(views().webui_tab_strip,
diff --git a/chrome/browser/ui/views/frame/shadow_overlay_view.cc b/chrome/browser/ui/views/frame/shadow_overlay_view.cc
index d3fc2ed..dc8ad0ac 100644
--- a/chrome/browser/ui/views/frame/shadow_overlay_view.cc
+++ b/chrome/browser/ui/views/frame/shadow_overlay_view.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/top_container_background.h"
 #include "chrome/browser/ui/views/side_panel/side_panel.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_animation_coordinator.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_animation_ids.h"
 #include "third_party/skia/include/core/SkPath.h"
 #include "third_party/skia/include/core/SkPathBuilder.h"
@@ -301,8 +302,6 @@
 
 void ShadowOverlayView::OnAnimationSequenceEnded(
     const SidePanelAnimationCoordinator::SidePanelAnimationId& animation_id) {
-  CHECK_EQ(kShadowOverlayOpacityAnimation, animation_id);
-
   // If we finish in the open state, the animation should be at 100%. If we
   // finish in the close state, the ShadowBox's shadow is removed entirely so
   // this line is a no-op.
diff --git a/chrome/browser/ui/views/frame/shadow_overlay_view.h b/chrome/browser/ui/views/frame/shadow_overlay_view.h
index b7761dd..36c1103 100644
--- a/chrome/browser/ui/views/frame/shadow_overlay_view.h
+++ b/chrome/browser/ui/views/frame/shadow_overlay_view.h
@@ -18,10 +18,11 @@
 
 // This view is responsible for framing the primary elements of the UI when
 // toolbar height side panel is showing, providing a nice drop shadow.
-class ShadowOverlayView : public views::View,
-                          public views::LayoutDelegate,
-                          public SidePanelAnimationCoordinator::Observer,
-                          public views::ViewObserver {
+class ShadowOverlayView
+    : public views::View,
+      public views::LayoutDelegate,
+      public SidePanelAnimationCoordinator::AnimationIdObserver,
+      public views::ViewObserver {
   METADATA_HEADER(ShadowOverlayView, views::View)
 
  public:
@@ -41,7 +42,7 @@
   views::ProposedLayout CalculateProposedLayout(
       const views::SizeBounds& size_bounds) const override;
 
-  // SidePanelAnimationCoordinator::Observer
+  // SidePanelAnimationCoordinator::AnimationIdObserver
   void OnAnimationSequenceProgressed(
       const SidePanelAnimationCoordinator::SidePanelAnimationId& animation_id,
       double animation_value) override;
diff --git a/chrome/browser/ui/views/infobars/BUILD.gn b/chrome/browser/ui/views/infobars/BUILD.gn
index 6d2c7fe5..ab54045 100644
--- a/chrome/browser/ui/views/infobars/BUILD.gn
+++ b/chrome/browser/ui/views/infobars/BUILD.gn
@@ -38,3 +38,29 @@
     "//ui/views",
   ]
 }
+
+source_set("browser_tests") {
+  testonly = true
+
+  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+
+  sources = [
+    "infobar_container_view_browsertest.cc",
+    "infobar_view_browsertest.cc",
+  ]
+
+  deps = [
+    ":impl",
+    ":infobars",
+    "//chrome/browser/ui",
+    "//chrome/browser/ui:ui_features",
+    "//chrome/test:test_support",
+    "//chrome/test:test_support_ui",
+    "//components/infobars/content",
+    "//components/infobars/core",
+    "//content/test:test_support",
+    "//ui/base",
+    "//ui/views",
+    "//ui/views:test_support",
+  ]
+}
diff --git a/chrome/browser/ui/views/infobars/infobar_container_view.cc b/chrome/browser/ui/views/infobars/infobar_container_view.cc
index 7880b24..cccba57 100644
--- a/chrome/browser/ui/views/infobars/infobar_container_view.cc
+++ b/chrome/browser/ui/views/infobars/infobar_container_view.cc
@@ -79,7 +79,7 @@
 constexpr int kSeparatorHeightDip = 1;
 
 InfoBarContainerView::InfoBarContainerView(Delegate* delegate)
-    : infobars::InfoBarContainer(delegate),
+    : infobars::InfoBarContainerWithPriority(delegate),
       content_shadow_(new ContentShadow()) {
   SetID(VIEW_ID_INFO_BAR_CONTAINER);
   AddChildViewRaw(content_shadow_.get());
diff --git a/chrome/browser/ui/views/infobars/infobar_container_view.h b/chrome/browser/ui/views/infobars/infobar_container_view.h
index 672bb57..ac3cfd9 100644
--- a/chrome/browser/ui/views/infobars/infobar_container_view.h
+++ b/chrome/browser/ui/views/infobars/infobar_container_view.h
@@ -8,14 +8,14 @@
 #include <stddef.h>
 
 #include "base/memory/raw_ptr.h"
-#include "components/infobars/core/infobar_container.h"
+#include "components/infobars/core/infobar_container_with_priority.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/accessible_pane_view.h"
 #include "ui/views/view_targeter_delegate.h"
 
 // The views-specific implementation of InfoBarContainer.
 class InfoBarContainerView : public views::AccessiblePaneView,
-                             public infobars::InfoBarContainer {
+                             public infobars::InfoBarContainerWithPriority {
   METADATA_HEADER(InfoBarContainerView, views::AccessiblePaneView)
 
  public:
diff --git a/chrome/browser/ui/views/infobars/infobar_container_view_browsertest.cc b/chrome/browser/ui/views/infobars/infobar_container_view_browsertest.cc
new file mode 100644
index 0000000..52086f76
--- /dev/null
+++ b/chrome/browser/ui/views/infobars/infobar_container_view_browsertest.cc
@@ -0,0 +1,436 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/infobars/infobar_container_view.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/tabs/split_tab_metrics.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/ui_features.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/infobars/confirm_infobar.h"
+#include "chrome/browser/ui/views/infobars/infobar_view.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/infobars/content/content_infobar_manager.h"
+#include "components/infobars/core/confirm_infobar_delegate.h"
+#include "components/infobars/core/features.h"
+#include "components/infobars/core/infobar.h"
+#include "components/infobars/core/infobar_manager.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/views/view_utils.h"
+
+namespace {
+
+class PriorityInfoBarDelegate : public ConfirmInfoBarDelegate {
+ public:
+  PriorityInfoBarDelegate(infobars::InfoBarDelegate::InfobarPriority priority,
+                          const std::u16string& message)
+      : priority_(priority), message_(message) {}
+
+  infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override {
+    return infobars::InfoBarDelegate::TEST_INFOBAR;
+  }
+
+  infobars::InfoBarDelegate::InfobarPriority GetPriority() const override {
+    return priority_;
+  }
+
+  std::u16string GetMessageText() const override { return message_; }
+
+  // Ensure each infobar is treated as unique.
+  bool EqualsDelegate(infobars::InfoBarDelegate* delegate) const override {
+    return false;
+  }
+
+ private:
+  const infobars::InfoBarDelegate::InfobarPriority priority_;
+  const std::u16string message_;
+};
+
+}  // namespace
+
+class InfoBarContainerViewBrowserTest : public InProcessBrowserTest {
+ public:
+  InfoBarContainerViewBrowserTest() = default;
+
+  InfoBarContainerView* GetInfoBarContainer() {
+    BrowserView* browser_view =
+        BrowserView::GetBrowserViewForBrowser(browser());
+    return browser_view->infobar_container();
+  }
+
+  infobars::InfoBarManager* GetInfoBarManager() {
+    return infobars::ContentInfoBarManager::FromWebContents(
+        browser()->tab_strip_model()->GetActiveWebContents());
+  }
+
+  infobars::InfoBar* AddInfoBar(
+      infobars::InfoBarDelegate::InfobarPriority priority,
+      const std::string& message) {
+    auto delegate = std::make_unique<PriorityInfoBarDelegate>(
+        priority, base::UTF8ToUTF16(message));
+    return GetInfoBarManager()->AddInfoBar(
+        std::make_unique<ConfirmInfoBar>(std::move(delegate)));
+  }
+
+  // Returns the message text of all currently visible infobar views.
+  std::vector<std::string> GetVisibleInfoBarMessages() {
+    std::vector<std::string> messages;
+    InfoBarContainerView* container = GetInfoBarContainer();
+    for (views::View* child : container->children()) {
+      if (auto* infobar_view = AsViewClass<InfoBarView>(child);
+          infobar_view && child->GetVisible()) {
+        if (auto* delegate =
+                infobar_view->delegate()->AsConfirmInfoBarDelegate()) {
+          messages.push_back(base::UTF16ToUTF8(delegate->GetMessageText()));
+        }
+      }
+    }
+    return messages;
+  }
+
+ protected:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+//
+// Tests for standard (non-prioritized) behavior.
+//
+class InfoBarContainerStandardTest : public InfoBarContainerViewBrowserTest {
+ public:
+  InfoBarContainerStandardTest() {
+    feature_list_.InitAndDisableFeature(infobars::kInfobarPrioritization);
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(InfoBarContainerStandardTest, AllAddedInfobarsAreShown) {
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kDefault, "InfoBar 1");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kCriticalSecurity,
+             "InfoBar 2");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kLow, "InfoBar 3");
+
+  // In standard mode, all infobars are visible regardless of priority.
+  std::vector<std::string> visible_messages = GetVisibleInfoBarMessages();
+  EXPECT_EQ(3u, visible_messages.size());
+  EXPECT_EQ("InfoBar 1", visible_messages[0]);
+  EXPECT_EQ("InfoBar 2", visible_messages[1]);
+  EXPECT_EQ("InfoBar 3", visible_messages[2]);
+}
+
+IN_PROC_BROWSER_TEST_F(InfoBarContainerStandardTest, RemoveInfoBar) {
+  infobars::InfoBar* infobar1 = AddInfoBar(
+      infobars::InfoBarDelegate::InfobarPriority::kDefault, "InfoBar 1");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kDefault, "InfoBar 2");
+
+  ASSERT_EQ(2u, GetVisibleInfoBarMessages().size());
+  GetInfoBarManager()->RemoveInfoBar(infobar1);
+
+  std::vector<std::string> visible_messages = GetVisibleInfoBarMessages();
+  EXPECT_EQ(1u, visible_messages.size());
+  EXPECT_EQ("InfoBar 2", visible_messages[0]);
+}
+
+IN_PROC_BROWSER_TEST_F(InfoBarContainerStandardTest, ReplaceInfoBar) {
+  infobars::InfoBar* old_bar = AddInfoBar(
+      infobars::InfoBarDelegate::InfobarPriority::kDefault, "Original Message");
+
+  ASSERT_EQ(1u, GetVisibleInfoBarMessages().size());
+
+  // Create a new delegate/infobar to replace the old one.
+  auto new_delegate = std::make_unique<PriorityInfoBarDelegate>(
+      infobars::InfoBarDelegate::InfobarPriority::kDefault,
+      u"Replacement Message");
+
+  GetInfoBarManager()->ReplaceInfoBar(
+      old_bar, std::make_unique<ConfirmInfoBar>(std::move(new_delegate)));
+
+  std::vector<std::string> messages = GetVisibleInfoBarMessages();
+  ASSERT_EQ(1u, messages.size());
+  EXPECT_EQ("Replacement Message", messages[0]);
+}
+
+IN_PROC_BROWSER_TEST_F(InfoBarContainerStandardTest,
+                       NavigationDismissesInfoBar) {
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kDefault, "Transient");
+  ASSERT_EQ(1u, GetVisibleInfoBarMessages().size());
+
+  // Navigate to a new URL. Most delegates (like ConfirmInfoBarDelegate)
+  // are configured to expire on navigation by default.
+  ASSERT_TRUE(
+      ui_test_utils::NavigateToURL(browser(), GURL("chrome://version")));
+
+  EXPECT_TRUE(GetInfoBarContainer()->IsEmpty());
+  EXPECT_TRUE(GetVisibleInfoBarMessages().empty());
+}
+
+//
+// Tests for priority-based behavior.
+//
+class InfoBarContainerPriorityTest : public InfoBarContainerViewBrowserTest {
+ public:
+  InfoBarContainerPriorityTest() {
+    // These caps match the design doc's defaults.
+    feature_list_.InitAndEnableFeatureWithParameters(
+        infobars::kInfobarPrioritization, {{"max_visible_critical", "2"},
+                                           {"max_visible_default", "1"},
+                                           {"max_visible_low", "1"}});
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(InfoBarContainerPriorityTest, CriticalStacksUpToCap) {
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kCriticalSecurity,
+             "Critical 1");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kCriticalSecurity,
+             "Critical 2");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kCriticalSecurity,
+             "Critical 3 (Queued)");
+
+  // Only the first two critical infobars should be visible.
+  std::vector<std::string> visible = GetVisibleInfoBarMessages();
+  EXPECT_EQ(2u, visible.size());
+  EXPECT_EQ("Critical 1", visible[0]);
+  EXPECT_EQ("Critical 2", visible[1]);
+}
+
+IN_PROC_BROWSER_TEST_F(InfoBarContainerPriorityTest, DefaultIsSingleVisible) {
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kDefault, "Default 1");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kDefault,
+             "Default 2 (Queued)");
+
+  // Only the first default infobar should be visible.
+  std::vector<std::string> visible = GetVisibleInfoBarMessages();
+  EXPECT_EQ(1u, visible.size());
+  EXPECT_EQ("Default 1", visible[0]);
+}
+
+IN_PROC_BROWSER_TEST_F(InfoBarContainerPriorityTest,
+                       CriticalBlocksDefaultAndLow) {
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kCriticalSecurity,
+             "Critical 1");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kDefault,
+             "Default (Queued)");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kLow, "Low (Queued)");
+
+  // While a critical infobar is visible, no lower-priority ones are shown.
+  std::vector<std::string> visible = GetVisibleInfoBarMessages();
+  EXPECT_EQ(1u, visible.size());
+  EXPECT_EQ("Critical 1", visible[0]);
+}
+
+IN_PROC_BROWSER_TEST_F(InfoBarContainerPriorityTest,
+                       PromotionAfterCriticalRemoved) {
+  infobars::InfoBar* critical_bar =
+      AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kCriticalSecurity,
+                 "Critical 1");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kLow, "Low (Queued)");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kDefault,
+             "Default (Queued)");
+
+  // Initial state: Only critical is visible.
+  ASSERT_EQ(1u, GetVisibleInfoBarMessages().size());
+  ASSERT_EQ("Critical 1", GetVisibleInfoBarMessages()[0]);
+
+  GetInfoBarManager()->RemoveInfoBar(critical_bar);
+
+  // After critical is removed, default should be promoted (higher priority
+  // than low).
+  std::vector<std::string> visible = GetVisibleInfoBarMessages();
+  EXPECT_EQ(1u, visible.size());
+  EXPECT_EQ("Default (Queued)", visible[0]);
+}
+
+IN_PROC_BROWSER_TEST_F(InfoBarContainerPriorityTest, FIFOWithinSamePriority) {
+  infobars::InfoBar* default1 = AddInfoBar(
+      infobars::InfoBarDelegate::InfobarPriority::kDefault, "Default 1");
+  infobars::InfoBar* default2 = AddInfoBar(
+      infobars::InfoBarDelegate::InfobarPriority::kDefault, "Default 2");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kDefault, "Default 3");
+
+  // Remove the first default infobar, the second one should appear.
+  GetInfoBarManager()->RemoveInfoBar(default1);
+  ASSERT_EQ(1u, GetVisibleInfoBarMessages().size());
+  EXPECT_EQ("Default 2", GetVisibleInfoBarMessages()[0]);
+
+  // Remove the second, the third should appear.
+  GetInfoBarManager()->RemoveInfoBar(default2);
+  ASSERT_EQ(1u, GetVisibleInfoBarMessages().size());
+  EXPECT_EQ("Default 3", GetVisibleInfoBarMessages()[0]);
+}
+
+IN_PROC_BROWSER_TEST_F(InfoBarContainerPriorityTest,
+                       DefaultQueuedIfLowIsVisible) {
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kLow, "Low 1");
+  ASSERT_EQ(1u, GetVisibleInfoBarMessages().size());
+  ASSERT_EQ("Low 1", GetVisibleInfoBarMessages()[0]);
+
+  // Adding a default infobar will queue it because a low-priority one is
+  // already occupying the single non-critical slot.
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kDefault,
+             "Default (Queued)");
+  EXPECT_EQ(1u, GetVisibleInfoBarMessages().size());
+  EXPECT_EQ("Low 1", GetVisibleInfoBarMessages()[0]);
+}
+
+IN_PROC_BROWSER_TEST_F(InfoBarContainerPriorityTest,
+                       TabSwitchingPreservesState) {
+  // Setup Tab 1 with a visible critical and a queued default infobar.
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kCriticalSecurity,
+             "Critical");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kDefault,
+             "Default (Queued)");
+  ASSERT_EQ(1u, GetVisibleInfoBarMessages().size());
+  ASSERT_EQ("Critical", GetVisibleInfoBarMessages()[0]);
+
+  // Open and switch to a new tab.
+  ASSERT_TRUE(AddTabAtIndex(1, GURL("about:blank"), ui::PAGE_TRANSITION_TYPED));
+  browser()->tab_strip_model()->ActivateTabAt(1);
+
+  // The new tab should have an existing, but empty, infobar container.
+  ASSERT_TRUE(GetInfoBarContainer()->IsEmpty());
+
+  // Switch back to the first tab.
+  browser()->tab_strip_model()->ActivateTabAt(0);
+
+  // The state should be preserved.
+  EXPECT_EQ(1u, GetVisibleInfoBarMessages().size());
+  EXPECT_EQ("Critical", GetVisibleInfoBarMessages()[0]);
+}
+
+//
+// Tests for split tab behavior, parameterized by whether prioritization is
+// enabled.
+//
+class InfoBarContainerSplitTabTest : public InfoBarContainerViewBrowserTest,
+                                     public testing::WithParamInterface<bool> {
+ public:
+  InfoBarContainerSplitTabTest() {
+    std::vector<base::test::FeatureRef> enabled_features = {
+        features::kSideBySide};
+    std::vector<base::test::FeatureRef> disabled_features;
+
+    if (IsPrioritizationEnabled()) {
+      enabled_features.push_back(infobars::kInfobarPrioritization);
+    } else {
+      disabled_features.push_back(infobars::kInfobarPrioritization);
+    }
+    feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  }
+
+  bool IsPrioritizationEnabled() const { return GetParam(); }
+
+ protected:
+  // Splits the tab at `index_to_split` with the currently active tab.
+  void SplitTabWithActive(int index_to_split) {
+    browser()->tab_strip_model()->AddToNewSplit(
+        {index_to_split},
+        split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kVertical,
+                                       0.5f),
+        split_tabs::SplitTabCreatedSource::kToolbarButton);
+  }
+
+  // Switches focus between the two panes of the active split tab.
+  void SwitchSplitTabFocus() {
+    TabStripModel* tab_strip_model = browser()->tab_strip_model();
+    ASSERT_TRUE(tab_strip_model->IsActiveTabSplit());
+
+    // In this test setup with exactly two tabs (0 and 1) involved in a split,
+    // switching focus simply means activating the other index.
+    const int active_index = tab_strip_model->active_index();
+    const int next_index = (active_index == 0) ? 1 : 0;
+
+    tab_strip_model->ActivateTabAt(
+        next_index, TabStripUserGestureDetails(
+                        TabStripUserGestureDetails::GestureType::kOther));
+  }
+};
+
+IN_PROC_BROWSER_TEST_P(InfoBarContainerSplitTabTest,
+                       InfobarsAreIndependentInSplitTabs) {
+  TabStripModel* tab_strip_model = browser()->tab_strip_model();
+
+  // 1. Set up two tabs. The browser starts with one tab, so navigate it and
+  // add one more.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
+  ASSERT_TRUE(
+      AddTabAtIndex(1, GURL("chrome://version"), ui::PAGE_TRANSITION_TYPED));
+  tab_strip_model->ActivateTabAt(1);
+  ASSERT_EQ(2, tab_strip_model->count());
+
+  // 2. Add infobars to the active tab (index 1).
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kCriticalSecurity,
+             "Critical on Tab 2");
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kDefault,
+             "Default on Tab 2");
+
+  if (IsPrioritizationEnabled()) {
+    ASSERT_EQ(1u, GetVisibleInfoBarMessages().size());
+    EXPECT_EQ("Critical on Tab 2", GetVisibleInfoBarMessages()[0]);
+  } else {
+    ASSERT_EQ(2u, GetVisibleInfoBarMessages().size());
+    EXPECT_EQ("Critical on Tab 2", GetVisibleInfoBarMessages()[0]);
+    EXPECT_EQ("Default on Tab 2", GetVisibleInfoBarMessages()[1]);
+  }
+
+  // 3. Split tab 0 with the active tab (tab 1).
+  SplitTabWithActive(0);
+  // The tabs are now split but remain as distinct indices in the model.
+  ASSERT_EQ(2, tab_strip_model->count());
+  ASSERT_TRUE(tab_strip_model->IsActiveTabSplit());
+
+  // 4. Verify the infobars are still visible in the active pane (Tab 1).
+  EXPECT_EQ(GURL("chrome://version"),
+            tab_strip_model->GetActiveWebContents()->GetURL());
+  if (IsPrioritizationEnabled()) {
+    EXPECT_EQ(1u, GetVisibleInfoBarMessages().size());
+  } else {
+    EXPECT_EQ(2u, GetVisibleInfoBarMessages().size());
+  }
+
+  // 5. Switch focus to the other pane (which was tab 0).
+  SwitchSplitTabFocus();
+  EXPECT_EQ(GURL("about:blank"),
+            tab_strip_model->GetActiveWebContents()->GetURL());
+
+  // 6. Verify this pane has no infobars.
+  EXPECT_TRUE(GetInfoBarContainer()->IsEmpty());
+  EXPECT_TRUE(GetVisibleInfoBarMessages().empty());
+
+  // 7. Add an infobar to this second pane.
+  AddInfoBar(infobars::InfoBarDelegate::InfobarPriority::kDefault,
+             "Default on Tab 1");
+  ASSERT_EQ(1u, GetVisibleInfoBarMessages().size());
+  EXPECT_EQ("Default on Tab 1", GetVisibleInfoBarMessages()[0]);
+
+  // 8. Switch focus back to the first pane and verify its state is unchanged.
+  SwitchSplitTabFocus();
+  EXPECT_EQ(GURL("chrome://version"),
+            tab_strip_model->GetActiveWebContents()->GetURL());
+  if (IsPrioritizationEnabled()) {
+    ASSERT_EQ(1u, GetVisibleInfoBarMessages().size());
+    EXPECT_EQ("Critical on Tab 2", GetVisibleInfoBarMessages()[0]);
+  } else {
+    ASSERT_EQ(2u, GetVisibleInfoBarMessages().size());
+    EXPECT_EQ("Critical on Tab 2", GetVisibleInfoBarMessages()[0]);
+    EXPECT_EQ("Default on Tab 2", GetVisibleInfoBarMessages()[1]);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    InfoBarContainerSplitTabTest,
+    testing::Bool(),
+    [](const testing::TestParamInfo<InfoBarContainerSplitTabTest::ParamType>&
+           info) {
+      return info.param ? "PrioritizationEnabled" : "PrioritizationDisabled";
+    });
diff --git a/chrome/browser/ui/views/omnibox/omnibox_aim_popup_webui_content.cc b/chrome/browser/ui/views/omnibox/omnibox_aim_popup_webui_content.cc
index 521bdf2..f949ab4 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_aim_popup_webui_content.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_aim_popup_webui_content.cc
@@ -84,6 +84,7 @@
 }
 
 void OmniboxAimPopupWebUIContent::OnClosedWithInput(const std::string& input) {
+  location_bar_view()->GetOmniboxView()->RevertAll();
   location_bar_view()->GetOmniboxView()->SetUserText(base::UTF8ToUTF16(input),
                                                      /*update_popup=*/false);
 }
diff --git a/chrome/browser/ui/views/omnibox/omnibox_context_menu.cc b/chrome/browser/ui/views/omnibox/omnibox_context_menu.cc
index 22cce0b..e7e4173 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_context_menu.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_context_menu.cc
@@ -19,12 +19,11 @@
 
 OmniboxContextMenu::OmniboxContextMenu(views::Widget* parent_widget,
                                        OmniboxPopupFileSelector* file_selector,
-                                       content::WebContents* web_contents,
-                                       base::RepeatingClosure on_menu_closed)
+                                       content::WebContents* web_contents)
     : parent_widget_(parent_widget),
-      controller_(std::make_unique<OmniboxContextMenuController>(file_selector,
-                                                                 web_contents)),
-      on_menu_closed_(std::move(on_menu_closed)) {
+      controller_(
+          std::make_unique<OmniboxContextMenuController>(file_selector,
+                                                         web_contents)) {
   std::unique_ptr<views::MenuItemView> menu =
       std::make_unique<views::MenuItemView>(this);
   menu_ = menu.get();
@@ -88,12 +87,6 @@
   return controller_->IsCommandIdVisible(command_id);
 }
 
-void OmniboxContextMenu::OnMenuClosed(views::MenuItemView* menu) {
-  if (menu->GetRootMenuItem() == menu && on_menu_closed_) {
-    on_menu_closed_.Run();
-  }
-}
-
 void OmniboxContextMenu::OnIconChanged(int command_id) {
   const std::optional<size_t> index =
       controller_->menu_model()->GetIndexOfCommandId(command_id);
diff --git a/chrome/browser/ui/views/omnibox/omnibox_context_menu.h b/chrome/browser/ui/views/omnibox/omnibox_context_menu.h
index 5f55f83..0fca067f 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_context_menu.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_context_menu.h
@@ -5,7 +5,6 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_OMNIBOX_OMNIBOX_CONTEXT_MENU_H_
 #define CHROME_BROWSER_UI_VIEWS_OMNIBOX_OMNIBOX_CONTEXT_MENU_H_
 
-#include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
 #include "chrome/browser/ui/omnibox/omnibox_context_menu_controller.h"
@@ -28,11 +27,9 @@
 class OmniboxContextMenu : public views::MenuDelegate,
                            public ui::MenuModelDelegate {
  public:
-  explicit OmniboxContextMenu(
-      views::Widget* parent_widget,
-      OmniboxPopupFileSelector* file_selector,
-      content::WebContents* web_contents,
-      base::RepeatingClosure on_menu_closed = base::RepeatingClosure());
+  explicit OmniboxContextMenu(views::Widget* parent_widget,
+                              OmniboxPopupFileSelector* file_selector,
+                              content::WebContents* web_contents);
 
   ~OmniboxContextMenu() override;
 
@@ -49,7 +46,6 @@
   int GetMaxWidthForMenu(views::MenuItemView* menu) override;
   bool IsCommandEnabled(int command_id) const override;
   bool IsCommandVisible(int command_id) const override;
-  void OnMenuClosed(views::MenuItemView* menu) override;
 
   // ui::MenuModelDelegate:
   void OnIconChanged(int command_id) override;
@@ -62,8 +58,6 @@
   std::unique_ptr<views::MenuRunner> menu_runner_;
   // The menu itself. This is owned by `menu_runner_`.
   raw_ptr<views::MenuItemView> menu_;
-  // Optional callback to run after ExecuteCommand is called.
-  base::RepeatingClosure on_menu_closed_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_OMNIBOX_OMNIBOX_CONTEXT_MENU_H_
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_webui_base_content.cc b/chrome/browser/ui/views/omnibox/omnibox_popup_webui_base_content.cc
index f4c7369..c7843205 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_popup_webui_base_content.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_popup_webui_base_content.cc
@@ -6,7 +6,6 @@
 
 #include <string_view>
 
-#include "base/functional/bind.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/omnibox/omnibox_controller.h"
@@ -105,9 +104,7 @@
       GetWidget(), location_bar_view_->GetOmniboxPopupFileSelector(),
       location_bar_view_->GetOmniboxPopupAimPresenter()
           ->GetWebUIContent()
-          ->GetWebContents(),
-      base::BindRepeating(&OmniboxPopupWebUIBaseContent::OnMenuClosed,
-                          base::Unretained(this)));
+          ->GetWebContents());
   context_menu_->RunMenuAt(point, ui::mojom::MenuSourceType::kMouse);
 }
 
@@ -161,9 +158,5 @@
   return false;
 }
 
-void OmniboxPopupWebUIBaseContent::OnMenuClosed() {
-  context_menu_.reset();
-}
-
 BEGIN_METADATA(OmniboxPopupWebUIBaseContent)
 END_METADATA
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_webui_base_content.h b/chrome/browser/ui/views/omnibox/omnibox_popup_webui_base_content.h
index 8851521..a0d15a766 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_popup_webui_base_content.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_popup_webui_base_content.h
@@ -76,8 +76,6 @@
   bool top_rounded_corners() const { return top_rounded_corners_; }
 
  private:
-  void OnMenuClosed();
-
   raw_ptr<OmniboxPopupPresenterBase> popup_presenter_ = nullptr;
   raw_ptr<LocationBarView> location_bar_view_ = nullptr;
   raw_ptr<OmniboxPopupPresenterBase> omnibox_popup_presenter_ = nullptr;
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button_browsertest.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button_browsertest.cc
index 24b699c..d3b59d4 100644
--- a/chrome/browser/ui/views/profiles/avatar_toolbar_button_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button_browsertest.cc
@@ -133,6 +133,12 @@
 std::u16string test_given_name() {
   return std::u16string(kTestGivenName);
 }
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+constexpr std::u16string_view kWorkBadge(u"Custom Label");
+std::u16string work_badge() {
+  return std::u16string(kWorkBadge);
+}
+#endif
 
 enum class ColorThemeType { kAutogeneratedTheme, kUserColor };
 
@@ -2385,82 +2391,28 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AvatarToolbarButtonEnterpriseBadgingBrowserTest,
-                       PRE_GreetingNotShownWhenManagementAccepted) {
+                       PRE_ManagedAccountFlowWithDefaultWorkBadge) {
   AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser());
   // Normal state.
   ASSERT_TRUE(avatar->GetText().empty());
 
-  Signin(u"work@managed.com", u"TestName");
-  EXPECT_EQ(avatar->GetText(),
-            l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_MAKING_CHROME_YOURS));
-}
-
-IN_PROC_BROWSER_TEST_F(AvatarToolbarButtonEnterpriseBadgingBrowserTest,
-                       GreetingNotShownWhenManagementAccepted) {
-  ASSERT_TRUE(
-      GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
-
-  AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser());
-  // Normal state.
-  ASSERT_TRUE(avatar->GetText().empty());
-
+  SigninWithImage(u"work@managed.com", test_given_name());
   enterprise_util::SetUserAcceptedAccountManagement(browser()->profile(), true);
-  // The greeting would only show when the image is loaded. Set the image to
-  // make sure we do not have a false positive later.
-  AddSignedInImage(
-      GetIdentityManager()->GetPrimaryAccountId(signin::ConsentLevel::kSignin));
-
-  // We do not expect a greeting to be shown if user accepted management.
-  EXPECT_EQ(avatar->GetText(), u"Work");
-}
-
-// TODO(crbug.com/331746545): Check flaky test issue on windows.
-#if BUILDFLAG(IS_WIN)
-#define MAYBE_PRE_GreetingShownWhenManagementNotAccepted \
-  DISABLED_PRE_GreetingShownWhenManagementNotAccepted
-#define MAYBE_GreetingShownWhenManagementNotAccepted \
-  DISABLED_GreetingShownWhenManagementNotAccepted
-#else
-#define MAYBE_PRE_GreetingShownWhenManagementNotAccepted \
-  PRE_GreetingShownWhenManagementNotAccepted
-#define MAYBE_GreetingShownWhenManagementNotAccepted \
-  GreetingShownWhenManagementNotAccepted
-#endif
-// Test makes sure the greeting is shown when the management badge is not shown
-// (management declined) in the profile avatar pill.
-IN_PROC_BROWSER_TEST_F(AvatarToolbarButtonEnterpriseBadgingBrowserTest,
-                       MAYBE_PRE_GreetingShownWhenManagementNotAccepted) {
-  AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser());
-  // Normal state.
-  ASSERT_TRUE(avatar->GetText().empty());
-
-  Signin(u"work@managed.com", test_given_name());
   EXPECT_EQ(avatar->GetText(),
             l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_MAKING_CHROME_YOURS));
 }
 
 IN_PROC_BROWSER_TEST_F(AvatarToolbarButtonEnterpriseBadgingBrowserTest,
-                       MAYBE_GreetingShownWhenManagementNotAccepted) {
+                       ManagedAccountFlowWithDefaultWorkBadge) {
   ASSERT_TRUE(
       GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
+
   // Disable the preferences about syncing the tabs and history to make the
   // avatar promo eligible.
   SetHistoryAndTabsSyncingPreference(/*enable_sync=*/false);
 
   AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser());
-  enterprise_util::SetUserAcceptedAccountManagement(browser()->profile(),
-                                                    false);
-
-  // The button is in a waiting for image state, the name is not yet displayed.
-  // At this point the user has not accepted management yet.
-  EXPECT_EQ(avatar->GetText(), std::u16string());
-
-  // The greeting will only show when the image is loaded.
-  AddSignedInImage(
-      GetIdentityManager()->GetPrimaryAccountId(signin::ConsentLevel::kSignin));
-
-  // Since the user has not accepted management, the greeting will still be
-  // shown.
+  // Greeting shown even for Managed users.
   EXPECT_EQ(avatar->GetText(),
             l10n_util::GetStringFUTF16(IDS_AVATAR_BUTTON_GREETING,
                                        test_given_name()));
@@ -2469,51 +2421,52 @@
   EXPECT_EQ(avatar->GetText(),
             l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_SYNC_HISTORY));
   avatar->ClearActiveStateForTesting();
-  // Once the name (or sync promo) is not shown anymore, we expect no text.
-  EXPECT_EQ(avatar->GetText(), std::u16string());
+  EXPECT_EQ(avatar->GetText(),
+            l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_WORK));
 }
 
 // TODO(crbug.com/331746545): Check flaky test issue on windows.
-// TODO(crbug.com/331746545): Check flaky test issue on windows.
 #if BUILDFLAG(IS_WIN)
-#define MAYBE_PRE_PRE_SignedInWithNewSessionKeepWorkBadge \
-  DISABLED_PRE_PRE_SignedInWithNewSessionKeepWorkBadge
-#define MAYBE_PRE_SignedInWithNewSessionKeepWorkBadge \
-  DISABLED_PRE_SignedInWithNewSessionKeepWorkBadge
-#define MAYBE_SignedInWithNewSessionKeepWorkBadge \
-  DISABLED_SignedInWithNewSessionKeepWorkBadge
+#define MAYBE_PRE_SignedInWithNewSessionKeepCustomWorkBadge \
+  DISABLED_PRE_SignedInWithNewSessionKeepCustomWorkBadge
+#define MAYBE_SignedInWithNewSessionKeepCustomWorkBadge \
+  DISABLED_SignedInWithNewSessionKeepCustomWorkBadge
 #else
-#define MAYBE_PRE_PRE_SignedInWithNewSessionKeepWorkBadge \
-  PRE_PRE_SignedInWithNewSessionKeepWorkBadge
-#define MAYBE_PRE_SignedInWithNewSessionKeepWorkBadge \
-  PRE_SignedInWithNewSessionKeepWorkBadge
-#define MAYBE_SignedInWithNewSessionKeepWorkBadge \
-  SignedInWithNewSessionKeepWorkBadge
+#define MAYBE_PRE_SignedInWithNewSessionKeepCustomWorkBadge \
+  PRE_SignedInWithNewSessionKeepCustomWorkBadge
+#define MAYBE_SignedInWithNewSessionKeepCustomWorkBadge \
+  SignedInWithNewSessionKeepCustomWorkBadge
 #endif
 // Tests the flow for a managed sign-in.
 IN_PROC_BROWSER_TEST_F(AvatarToolbarButtonEnterpriseBadgingBrowserTest,
-                       MAYBE_PRE_PRE_SignedInWithNewSessionKeepWorkBadge) {
+                       MAYBE_PRE_SignedInWithNewSessionKeepCustomWorkBadge) {
   // Sign in.
   AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser());
   AccountInfo account_info =
       SigninWithImage(u"work@managed.com", test_given_name());
 
+  // Accept management and prepare work badge.
+  enterprise_util::SetUserAcceptedAccountManagement(browser()->profile(), true);
+  browser()->profile()->GetPrefs()->SetString(
+      prefs::kEnterpriseCustomLabelForProfile, base::UTF16ToUTF8(work_badge()));
+
   EXPECT_EQ(avatar->GetText(),
             l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_MAKING_CHROME_YOURS));
 }
 
+// Test that the work badge remains upon restart for a user that is managed.
 IN_PROC_BROWSER_TEST_F(AvatarToolbarButtonEnterpriseBadgingBrowserTest,
-                       MAYBE_PRE_SignedInWithNewSessionKeepWorkBadge) {
+                       MAYBE_SignedInWithNewSessionKeepCustomWorkBadge) {
   ASSERT_TRUE(
       GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
+  ASSERT_TRUE(
+      enterprise_util::UserAcceptedAccountManagement(browser()->profile()));
 
   // Disable the preferences about syncing the tabs and history to make the
   // avatar promo eligible.
   SetHistoryAndTabsSyncingPreference(/*enable_sync=*/false);
 
   AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser());
-  // Since the user has not accepted management yet, the greeting will be
-  // shown.
   EXPECT_EQ(avatar->GetText(),
             l10n_util::GetStringFUTF16(IDS_AVATAR_BUTTON_GREETING,
                                        test_given_name()));
@@ -2523,50 +2476,15 @@
             l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_SYNC_HISTORY));
   avatar->ClearActiveStateForTesting();
 
-  // Once the name (or sync promo) is not shown anymore, we expect no text since
-  // management is not accepted.
-  EXPECT_EQ(avatar->GetText(), std::u16string());
-
-  // Management is usually accepted by the time the greeting is finished. The
-  // work badgge should be shown once this happens.
-  enterprise_util::SetUserAcceptedAccountManagement(browser()->profile(), true);
-  browser()->profile()->GetPrefs()->SetString(
-      prefs::kEnterpriseCustomLabelForProfile, "Custom Label");
-
-  EXPECT_EQ(avatar->GetText(), u"Custom Label");
-}
-
-// Test that the work badge remains upon restart for a user that is managed.
-// Note that we need to unset and reset UserAcceptedAccountManagement due to the
-// management service override.
-IN_PROC_BROWSER_TEST_F(AvatarToolbarButtonEnterpriseBadgingBrowserTest,
-                       MAYBE_SignedInWithNewSessionKeepWorkBadge) {
-  // Disable the preferences about syncing the tabs and history to make the
-  // avatar promo eligible.
-  SetHistoryAndTabsSyncingPreference(/*enable_sync=*/false);
-
-  AvatarToolbarButton* avatar = GetAvatarToolbarButton(browser());
-  // The greetings are shown due to the management service override (unaware of
-  // the management acceptance after restart).
-  EXPECT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16(
-                                   IDS_AVATAR_BUTTON_GREETING, u"TestName"));
-  avatar->ClearActiveStateForTesting();
-  // The greeting is followed by the history sync opt-in.
-  EXPECT_EQ(avatar->GetText(),
-            l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_SYNC_HISTORY));
-  avatar->ClearActiveStateForTesting();
-
-  enterprise_util::SetUserAcceptedAccountManagement(browser()->profile(),
-                                                    false);
-  enterprise_util::SetUserAcceptedAccountManagement(browser()->profile(), true);
-
-  EXPECT_EQ(avatar->GetText(), u"Custom Label");
+  // Once the promo is not shown anymore, we expect the work badge to be shown.
+  EXPECT_EQ(avatar->GetText(), work_badge());
   EXPECT_EQ(GetProfileAttributesEntry(browser()->profile())
                 ->GetEnterpriseProfileLabel(),
-            u"Custom Label");
+            work_badge());
   EXPECT_EQ(
       GetProfileAttributesEntry(browser()->profile())->GetLocalProfileName(),
-      u"Custom Label");
+      work_badge());
+
   // Previously added image on signin should still be shown in the new session.
   EXPECT_TRUE(IsSignedInImageUsed());
 }
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button_state_manager.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button_state_manager.cc
index d9dc45e..21a672d 100644
--- a/chrome/browser/ui/views/profiles/avatar_toolbar_button_state_manager.cc
+++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button_state_manager.cc
@@ -742,12 +742,6 @@
   // Shows the name in the identity pill. If the name is already showing, this
   // extends the duration.
   void ShowIdentityName() {
-    // Do not show the identity name if the enterprise badging is enabled for
-    // the avatar.
-    if (enterprise_util::CanShowEnterpriseBadgingForAvatar(&profile())) {
-      return;
-    }
-
     ++show_identity_request_count_;
     waiting_for_image_ = false;
 
diff --git a/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator_browsertest.cc b/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator_browsertest.cc
index 248ea36..6403eef 100644
--- a/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator_browsertest.cc
+++ b/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator_browsertest.cc
@@ -19,6 +19,10 @@
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chromeos/constants/chromeos_features.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 namespace glic {
 namespace {
 
@@ -34,8 +38,16 @@
  public:
   GlicSidePanelCoordinatorTest() {
     scoped_feature_list_.InitWithFeatures(
-        {features::kGlic, features::kGlicRollout,
-         features::kTabstripComboButton, features::kGlicMultiInstance},
+        {
+            features::kGlic,
+            features::kGlicRollout,
+            features::kTabstripComboButton,
+            features::kGlicMultiInstance,
+#if BUILDFLAG(IS_CHROMEOS)
+            chromeos::features::kFeatureManagementGlic,
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
+        },
         {});
   }
 
diff --git a/chrome/browser/ui/views/side_panel/side_panel.cc b/chrome/browser/ui/views/side_panel/side_panel.cc
index 8a128e29..a00eae28 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel.cc
@@ -579,10 +579,8 @@
 
 void SidePanel::OnAnimationSequenceEnded(
     const SidePanelAnimationCoordinator::SidePanelAnimationId& animation_id) {
-  CHECK_EQ(kSidePanelBoundsAnimation, animation_id)
-      << "Observed animation id is not handled";
-
-  if (animation_coordinator_->GetAnimationValueFor(animation_id) != 0) {
+  if (animation_coordinator_->GetAnimationValueFor(kSidePanelBoundsAnimation) !=
+      0) {
     state_ = State::kOpen;
   } else {
     state_ = State::kClosed;
diff --git a/chrome/browser/ui/views/side_panel/side_panel.h b/chrome/browser/ui/views/side_panel/side_panel.h
index ced9466..0776ecb 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.h
+++ b/chrome/browser/ui/views/side_panel/side_panel.h
@@ -24,7 +24,7 @@
 
 class SidePanel : public views::AccessiblePaneView,
                   public views::ResizeAreaDelegate,
-                  public SidePanelAnimationCoordinator::Observer {
+                  public SidePanelAnimationCoordinator::AnimationIdObserver {
   METADATA_HEADER(SidePanel, views::AccessiblePaneView)
 
  public:
@@ -118,7 +118,7 @@
   // views::View:
   void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
 
-  // SidePanelAnimationCoordinator::AnimationObserver
+  // SidePanelAnimationCoordinator::AnimationIdObserver
   void OnAnimationSequenceProgressed(
       const SidePanelAnimationCoordinator::SidePanelAnimationId& animation_id,
       double animation_value) override;
diff --git a/chrome/browser/ui/views/side_panel/side_panel_animation_coordinator.cc b/chrome/browser/ui/views/side_panel/side_panel_animation_coordinator.cc
index c607fba..7e73ee77 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_animation_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_animation_coordinator.cc
@@ -120,6 +120,9 @@
       {AnimationType::kOpen, open_animation_specifications},
       {AnimationType::kClose, close_animation_specifications}};
 
+  animation_type_to_observer_map_[AnimationType::kOpen] = {};
+  animation_type_to_observer_map_[AnimationType::kClose] = {};
+
   Reset(AnimationType::kClose);
 }
 
@@ -130,9 +133,12 @@
     return;
   }
 
+  notified_ended_animations_.clear();
   animation_type_ = type;
   animation_.SetSlideDuration(GetAnimationDuration(type));
 
+  NotifyAnimationTypeStartedObservers();
+
   if (IsAnimatingOpen(type)) {
     animation_.Show();
   } else {
@@ -141,6 +147,7 @@
 }
 
 void SidePanelAnimationCoordinator::Reset(AnimationType type) {
+  notified_ended_animations_.clear();
   animation_type_ = type;
 
   if (IsAnimatingOpen(type)) {
@@ -152,17 +159,30 @@
 
 void SidePanelAnimationCoordinator::AddObserver(
     const SidePanelAnimationId& animation_id,
-    Observer* observer) {
-  animation_id_to_observer_map_[animation_id].push_back(observer);
+    AnimationIdObserver* observer) {
+  animation_id_to_observer_map_[animation_id].emplace(observer);
 }
 
 void SidePanelAnimationCoordinator::RemoveObserver(
     const SidePanelAnimationId& animation_id,
-    Observer* observer) {
-  auto& observers = animation_id_to_observer_map_[animation_id];
-  auto it = std::ranges::find(observers, observer);
-  CHECK(it != observers.end());
-  observers.erase(it);
+    AnimationIdObserver* observer) {
+  CHECK(animation_id_to_observer_map_.contains(animation_id))
+      << "Observer was not added for: " << animation_id.GetName();
+  animation_id_to_observer_map_[animation_id].erase(observer);
+}
+
+void SidePanelAnimationCoordinator::AddObserver(
+    AnimationType type,
+    AnimationTypeObserver* observer) {
+  animation_type_to_observer_map_[type].emplace(observer);
+}
+
+void SidePanelAnimationCoordinator::RemoveObserver(
+    AnimationType type,
+    AnimationTypeObserver* observer) {
+  CHECK(animation_type_to_observer_map_.at(type).contains(observer))
+      << "Observer was not added for type";
+  animation_type_to_observer_map_[type].erase(observer);
 }
 
 double SidePanelAnimationCoordinator::GetAnimationValueFor(
@@ -209,27 +229,30 @@
     const gfx::Animation* animation) {
   for (auto& [animation_id, observers] : animation_id_to_observer_map_) {
     if (!IsAnimationSequenceRunning(animation_id)) {
+      NotifyOnSequenceEndedObservers(animation_id, observers);
       continue;
     }
 
-    for (Observer* observer : observers) {
-      observer->OnAnimationSequenceProgressed(
-          animation_id, GetAnimationValueFor(animation_id));
+    double animation_value = GetAnimationValueFor(animation_id);
+    for (AnimationIdObserver* observer : observers) {
+      observer->OnAnimationSequenceProgressed(animation_id, animation_value);
     }
   }
 }
 
 void SidePanelAnimationCoordinator::AnimationEnded(
     const gfx::Animation* animation) {
+  // Ensure all animation observers are notified if not already.
   for (auto& [animation_id, observers] : animation_id_to_observer_map_) {
-    if (!IsAnimationSequenceFinished(animation_id)) {
-      continue;
-    }
-
-    for (Observer* observer : observers) {
-      observer->OnAnimationSequenceEnded(animation_id);
-    }
+    NotifyOnSequenceEndedObservers(animation_id, observers);
   }
+
+  NotifyAnimationTypeEndedObservers();
+}
+
+void SidePanelAnimationCoordinator::AnimationCanceled(
+    const gfx::Animation* animation) {
+  AnimationEnded(animation);
 }
 
 double SidePanelAnimationCoordinator::AdjustProgressForAnimationType(
@@ -292,3 +315,36 @@
 
   return animation_specification;
 }
+
+const std::set<raw_ptr<SidePanelAnimationCoordinator::AnimationTypeObserver>>&
+SidePanelAnimationCoordinator::GetAnimationTypeObservers() {
+  CHECK(animation_type_to_observer_map_.contains(animation_type_))
+      << "The animation type must be prepopulated in the constructor";
+  return animation_type_to_observer_map_.at(animation_type_);
+}
+
+void SidePanelAnimationCoordinator::NotifyOnSequenceEndedObservers(
+    const SidePanelAnimationId& animation_id,
+    const std::set<raw_ptr<AnimationIdObserver>> observers) {
+  if (!IsAnimationSequenceFinished(animation_id) ||
+      notified_ended_animations_.contains(animation_id)) {
+    return;
+  }
+
+  notified_ended_animations_.insert(animation_id);
+  for (AnimationIdObserver* observer : observers) {
+    observer->OnAnimationSequenceEnded(animation_id);
+  }
+}
+
+void SidePanelAnimationCoordinator::NotifyAnimationTypeStartedObservers() {
+  for (AnimationTypeObserver* observer : GetAnimationTypeObservers()) {
+    observer->OnAnimationTypeStarted(animation_type_);
+  }
+}
+
+void SidePanelAnimationCoordinator::NotifyAnimationTypeEndedObservers() {
+  for (AnimationTypeObserver* observer : GetAnimationTypeObservers()) {
+    observer->OnAnimationTypeEnded(animation_type_);
+  }
+}
diff --git a/chrome/browser/ui/views/side_panel/side_panel_animation_coordinator.h b/chrome/browser/ui/views/side_panel/side_panel_animation_coordinator.h
index d978a55..dcfa11a 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_animation_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_animation_coordinator.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_ANIMATION_COORDINATOR_H_
 #define CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_ANIMATION_COORDINATOR_H_
 
+#include <set>
 #include <vector>
 
 #include "base/observer_list_types.h"
@@ -17,6 +18,10 @@
 
 class SidePanel;
 
+namespace gfx {
+class Animation;
+}
+
 // Coordinates multiple animations that need to be synchronized on a single
 // timeline for the side panel. This class is useful for creating complex,
 // multi-part animations where different elements animate in parallel or in
@@ -72,18 +77,29 @@
     std::vector<AnimationSequence> sequences;
   };
 
-  class Observer : public base::CheckedObserver {
+  // Used to observe changes to a particular animation id.
+  class AnimationIdObserver : public base::CheckedObserver {
    public:
-    // Called when the animation coordinator is progressed.
+    // Called when the animation sequence for `animation_id` has progressed.
     virtual void OnAnimationSequenceProgressed(
         const SidePanelAnimationId& animation_id,
         double animation_value) {}
 
-    // Called when the animation coordinator is ended.
+    // Called when the animation sequence for `animation_id` has ended.
     virtual void OnAnimationSequenceEnded(
         const SidePanelAnimationId& animation_id) {}
   };
 
+  // Used to observe changes to a particular animation type.
+  class AnimationTypeObserver : public base::CheckedObserver {
+   public:
+    // Called when the coordinator's animation has started for `type`.
+    virtual void OnAnimationTypeStarted(AnimationType type) {}
+
+    // Called when the animation coordinator has ended for `type`.
+    virtual void OnAnimationTypeEnded(AnimationType type) {}
+  };
+
   explicit SidePanelAnimationCoordinator(SidePanel* side_panel);
   SidePanelAnimationCoordinator(const SidePanelAnimationCoordinator&) = delete;
   SidePanelAnimationCoordinator& operator=(
@@ -95,10 +111,15 @@
   // Snap to the end state for the animations associated with `type`.
   void Reset(AnimationType type);
 
+  // Add / Remove observers for a specific animation id.
   void AddObserver(const SidePanelAnimationId& animation_id,
-                   Observer* observer);
+                   AnimationIdObserver* observer);
   void RemoveObserver(const SidePanelAnimationId& animation_id,
-                      Observer* observer);
+                      AnimationIdObserver* observer);
+
+  // Add / Remove observers for a specific animation type.
+  void AddObserver(AnimationType type, AnimationTypeObserver* observer);
+  void RemoveObserver(AnimationType type, AnimationTypeObserver* observer);
 
   // Returns the animation value for `animation_id` based on it's
   // AnimationSpecification's tween / animation curve. Will always return 0 for
@@ -114,10 +135,13 @@
     return animation_spec_map_;
   }
 
+  gfx::Animation* animation_for_testing() { return &animation_; }
+
  private:
   // views::AnimationDelegateViews
   void AnimationProgressed(const gfx::Animation* animation) override;
   void AnimationEnded(const gfx::Animation* animation) override;
+  void AnimationCanceled(const gfx::Animation* animation) override;
 
   double AdjustProgressForAnimationType(double progress) const;
   base::TimeDelta GetElapsedAnimationTime() const;
@@ -137,6 +161,27 @@
   GetAnimationSpecificationForAnimationId(
       const SidePanelAnimationId& animation_id);
 
+  // Returns the set of observers listening to the current animation type. These
+  // observers will be notified of changes to the main animation state before
+  // AnimationProgressed is called.
+  const std::set<raw_ptr<AnimationTypeObserver>>& GetAnimationTypeObservers();
+
+  // Notifies observers that an animation sequence has ended for `animation_id`.
+  // If the observer has already been notified, do nothing.
+  void NotifyOnSequenceEndedObservers(
+      const SidePanelAnimationId& animation_id,
+      const std::set<raw_ptr<AnimationIdObserver>> observers);
+
+  // Notifies observers that the animation for `animation_type_` will start.
+  // This gives observers a chance to set prerequisite states before
+  // AnimationProgressed is called.
+  void NotifyAnimationTypeStartedObservers();
+
+  // Notifies observers that the animation for `animation_type_` has ended. This
+  // gives observers a chance to set final states after AnimationEnded /
+  // AnimationCanceled is called.
+  void NotifyAnimationTypeEndedObservers();
+
   // The current AnimationType of the coordinator.
   AnimationType animation_type_ = AnimationType::kClose;
 
@@ -144,10 +189,19 @@
   // for that type.
   std::map<AnimationType, AnimationSpecification> animation_spec_map_;
 
-  // Maps the property id to the list of its observers.
-  std::map<SidePanelAnimationId, std::vector<raw_ptr<Observer>>>
+  // Maps the animation id to the set of its observers.
+  std::map<SidePanelAnimationId, std::set<raw_ptr<AnimationIdObserver>>>
       animation_id_to_observer_map_;
 
+  // Maps the animation type to the set of observers
+  std::map<AnimationType, std::set<raw_ptr<AnimationTypeObserver>>>
+      animation_type_to_observer_map_;
+
+  // Set of animation ids that have already notified their observers that they
+  // have ended for the current animation cycle. AnimationIdObservers are only
+  // notified if a sequence has ended once.
+  std::set<SidePanelAnimationId> notified_ended_animations_;
+
   // Linear animation used to coordinate all of the other animations
   // added to this class. This serves as the source of truth timeline
   // for the class.
diff --git a/chrome/browser/ui/views/side_panel/side_panel_animation_coordinator_browsertest.cc b/chrome/browser/ui/views/side_panel/side_panel_animation_coordinator_browsertest.cc
index b9877220..faeb720 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_animation_coordinator_browsertest.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_animation_coordinator_browsertest.cc
@@ -6,6 +6,7 @@
 
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
+#include "base/time/time.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/side_panel/side_panel.h"
@@ -15,13 +16,15 @@
 #include "content/public/test/browser_test.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/base/interaction/element_identifier.h"
+#include "ui/gfx/animation/animation_test_api.h"
 #include "ui/views/layout/animating_layout_manager_test_util.h"
 #include "ui/views/test/views_test_utils.h"
 
 using ::testing::_;
 
 class MockSidePanelAnimationObserver
-    : public SidePanelAnimationCoordinator::Observer {
+    : public SidePanelAnimationCoordinator::AnimationIdObserver,
+      public SidePanelAnimationCoordinator::AnimationTypeObserver {
  public:
   MOCK_METHOD(
       void,
@@ -34,18 +37,34 @@
       OnAnimationSequenceEnded,
       (const SidePanelAnimationCoordinator::SidePanelAnimationId& animation_id),
       (override));
+  MOCK_METHOD(void,
+              OnAnimationTypeEnded,
+              (const SidePanelAnimationCoordinator::AnimationType),
+              (override));
+  MOCK_METHOD(void,
+              OnAnimationTypeStarted,
+              (const SidePanelAnimationCoordinator::AnimationType),
+              (override));
 };
 
-class SidePanelAnimationCoordinatorTest : public InProcessBrowserTest {
+class SidePanelAnimationCoordinatorBrowserTest : public InProcessBrowserTest {
  public:
   SidePanelAnimationCoordinator* GetAnimationCoordinator() {
     return BrowserView::GetBrowserViewForBrowser(browser())
         ->toolbar_height_side_panel()
         ->animation_coordinator();
   }
+
+  void AddAnimationSequence(
+      SidePanelAnimationCoordinator::AnimationType type,
+      SidePanelAnimationCoordinator::AnimationSequence sequence) {
+    auto& animation_spec_map =
+        GetAnimationCoordinator()->animation_spec_map_for_testing();
+    animation_spec_map.at(type).sequences.push_back(sequence);
+  }
 };
 
-IN_PROC_BROWSER_TEST_F(SidePanelAnimationCoordinatorTest,
+IN_PROC_BROWSER_TEST_F(SidePanelAnimationCoordinatorBrowserTest,
                        AnimationProgressedWithMismatchedAnimationIds) {
   SidePanelAnimationCoordinator* animation_coordinator =
       GetAnimationCoordinator();
@@ -53,53 +72,394 @@
   DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestAnimationId);
 
   // Add a test animation that only exists for the kOpen animation type.
-  auto& animation_spec_map =
-      animation_coordinator->animation_spec_map_for_testing();
-  auto& open_sequences =
-      animation_spec_map.at(SidePanelAnimationCoordinator::AnimationType::kOpen)
-          .sequences;
-  open_sequences.push_back({.animation_id = kTestAnimationId,
-                            .start = base::Milliseconds(0),
-                            .duration = base::Milliseconds(100)});
+  AddAnimationSequence(SidePanelAnimationCoordinator::AnimationType::kOpen,
+                       {.animation_id = kTestAnimationId,
+                        .start = base::Milliseconds(0),
+                        .duration = base::Milliseconds(100)});
 
-  MockSidePanelAnimationObserver bounds_observer;
+  MockSidePanelAnimationObserver control_observer;
   MockSidePanelAnimationObserver test_observer;
-  animation_coordinator->AddObserver(kSidePanelBoundsAnimation,
-                                     &bounds_observer);
-  animation_coordinator->AddObserver(kTestAnimationId, &test_observer);
+  animation_coordinator->AddObserver(
+      SidePanelAnimationCoordinator::AnimationType::kOpen, &control_observer);
+  animation_coordinator->AddObserver(
+      SidePanelAnimationCoordinator::AnimationType::kClose, &control_observer);
+  animation_coordinator->AddObserver(
+      SidePanelAnimationCoordinator::AnimationType::kOpen, &test_observer);
 
   // The bounds animation and the test animation should run when opening the
   // side panel.
-  base::test::TestFuture<void> bounds_animation_ended;
-  EXPECT_CALL(bounds_observer,
-              OnAnimationSequenceEnded(kSidePanelBoundsAnimation))
+  base::test::TestFuture<void> control_animation_ended;
+  EXPECT_CALL(
+      control_observer,
+      OnAnimationTypeEnded(SidePanelAnimationCoordinator::AnimationType::kOpen))
       .WillOnce(
-          [&bounds_animation_ended]() { bounds_animation_ended.SetValue(); });
+          [&control_animation_ended]() { control_animation_ended.SetValue(); });
 
   base::test::TestFuture<void> test_animation_ended;
-  EXPECT_CALL(test_observer, OnAnimationSequenceEnded(kTestAnimationId))
+  EXPECT_CALL(
+      test_observer,
+      OnAnimationTypeEnded(SidePanelAnimationCoordinator::AnimationType::kOpen))
       .WillOnce([&test_animation_ended]() { test_animation_ended.SetValue(); });
 
   animation_coordinator->Start(
       SidePanelAnimationCoordinator::AnimationType::kOpen);
 
-  EXPECT_TRUE(bounds_animation_ended.Wait());
+  EXPECT_TRUE(control_animation_ended.Wait());
   EXPECT_TRUE(test_animation_ended.Wait());
 
-  bounds_animation_ended.Clear();
+  control_animation_ended.Clear();
   test_animation_ended.Clear();
 
   // Only the bounds animation should run when closing the side panel.
-  EXPECT_CALL(bounds_observer,
-              OnAnimationSequenceEnded(kSidePanelBoundsAnimation))
+  EXPECT_CALL(control_observer,
+              OnAnimationTypeEnded(
+                  SidePanelAnimationCoordinator::AnimationType::kClose))
       .WillOnce(
-          [&bounds_animation_ended]() { bounds_animation_ended.SetValue(); });
+          [&control_animation_ended]() { control_animation_ended.SetValue(); });
 
-  EXPECT_CALL(test_observer, OnAnimationSequenceEnded(kTestAnimationId))
+  EXPECT_CALL(test_observer,
+              OnAnimationTypeEnded(
+                  SidePanelAnimationCoordinator::AnimationType::kClose))
       .Times(0);
 
   animation_coordinator->Start(
       SidePanelAnimationCoordinator::AnimationType::kClose);
 
-  EXPECT_TRUE(bounds_animation_ended.Wait());
+  EXPECT_TRUE(control_animation_ended.Wait());
+}
+
+IN_PROC_BROWSER_TEST_F(SidePanelAnimationCoordinatorBrowserTest,
+                       AnimationIdObserversDoNotFireTypeCallbacks) {
+  SidePanelAnimationCoordinator* animation_coordinator =
+      GetAnimationCoordinator();
+
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestAnimationId);
+  AddAnimationSequence(SidePanelAnimationCoordinator::AnimationType::kOpen,
+                       {.animation_id = kTestAnimationId,
+                        .start = base::Milliseconds(0),
+                        .duration = base::Milliseconds(100)});
+  AddAnimationSequence(SidePanelAnimationCoordinator::AnimationType::kClose,
+                       {.animation_id = kTestAnimationId,
+                        .start = base::Milliseconds(0),
+                        .duration = base::Milliseconds(100)});
+
+  MockSidePanelAnimationObserver target_observer;
+  animation_coordinator->AddObserver(kTestAnimationId, &target_observer);
+
+  // The control observer is needed to notify us when the entire animation has
+  // finished so we can start the close animation. Without
+  // this observer the test animation will finish before the side panel bounds
+  // animation and will immediately trigger the close animation. The close
+  // animation will not run properly since it will be mid-animation (on some
+  // machines).
+  //
+  // This results in receiving the OnAnimationSequenceEnded event
+  // before the OnAnimationSequenceProgressed event effectively ending the test
+  // and causing test failures since progressed events were never received.
+  //
+  // In practice, this has no visual effect since the OnAnimationSequenceEnded
+  // observers should put their UI in the correct final state.
+  MockSidePanelAnimationObserver control_observer;
+  animation_coordinator->AddObserver(
+      SidePanelAnimationCoordinator::AnimationType::kOpen, &control_observer);
+  animation_coordinator->AddObserver(
+      SidePanelAnimationCoordinator::AnimationType::kClose, &control_observer);
+
+  base::test::TestFuture<void> target_animation_finished;
+  base::test::TestFuture<void> control_animation_finished;
+
+  EXPECT_CALL(target_observer,
+              OnAnimationSequenceProgressed(kTestAnimationId, _))
+      .Times(testing::AtLeast(1));
+  EXPECT_CALL(target_observer, OnAnimationSequenceEnded(kTestAnimationId))
+      .WillOnce([&target_animation_finished]() {
+        target_animation_finished.SetValue();
+      });
+
+  EXPECT_CALL(target_observer, OnAnimationTypeStarted(_)).Times(0);
+  EXPECT_CALL(target_observer, OnAnimationTypeEnded(_)).Times(0);
+
+  EXPECT_CALL(
+      control_observer,
+      OnAnimationTypeEnded(SidePanelAnimationCoordinator::AnimationType::kOpen))
+      .WillOnce([&control_animation_finished]() {
+        control_animation_finished.SetValue();
+      });
+
+  animation_coordinator->Start(
+      SidePanelAnimationCoordinator::AnimationType::kOpen);
+
+  EXPECT_TRUE(target_animation_finished.Wait());
+  EXPECT_TRUE(control_animation_finished.Wait());
+
+  target_animation_finished.Clear();
+  control_animation_finished.Clear();
+
+  EXPECT_CALL(target_observer,
+              OnAnimationSequenceProgressed(kTestAnimationId, _))
+      .Times(testing::AtLeast(1));
+  EXPECT_CALL(target_observer, OnAnimationSequenceEnded(kTestAnimationId))
+      .WillOnce([&target_animation_finished]() {
+        target_animation_finished.SetValue();
+      });
+
+  EXPECT_CALL(target_observer, OnAnimationTypeStarted(_)).Times(0);
+
+  EXPECT_CALL(control_observer,
+              OnAnimationTypeEnded(
+                  SidePanelAnimationCoordinator::AnimationType::kClose))
+      .WillOnce([&control_animation_finished]() {
+        control_animation_finished.SetValue();
+      });
+
+  animation_coordinator->Start(
+      SidePanelAnimationCoordinator::AnimationType::kClose);
+
+  EXPECT_TRUE(target_animation_finished.Wait());
+  EXPECT_TRUE(control_animation_finished.Wait());
+}
+
+IN_PROC_BROWSER_TEST_F(SidePanelAnimationCoordinatorBrowserTest,
+                       AnimationTypeObserversFireStartedAndEnded) {
+  SidePanelAnimationCoordinator* animation_coordinator =
+      GetAnimationCoordinator();
+
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestAnimationId);
+
+  // Add a test animation for open and close animation type.
+  AddAnimationSequence(SidePanelAnimationCoordinator::AnimationType::kOpen,
+                       {.animation_id = kTestAnimationId,
+                        .start = base::Milliseconds(0),
+                        .duration = base::Milliseconds(100)});
+  AddAnimationSequence(SidePanelAnimationCoordinator::AnimationType::kClose,
+                       {.animation_id = kTestAnimationId,
+                        .start = base::Milliseconds(0),
+                        .duration = base::Milliseconds(100)});
+
+  MockSidePanelAnimationObserver test_observer;
+  animation_coordinator->AddObserver(
+      SidePanelAnimationCoordinator::AnimationType::kOpen, &test_observer);
+  animation_coordinator->AddObserver(
+      SidePanelAnimationCoordinator::AnimationType::kClose, &test_observer);
+
+  base::test::TestFuture<void> animation_started;
+  EXPECT_CALL(test_observer,
+              OnAnimationTypeStarted(
+                  SidePanelAnimationCoordinator::AnimationType::kOpen))
+      .WillOnce([&animation_started]() { animation_started.SetValue(); });
+
+  base::test::TestFuture<void> animation_ended;
+  EXPECT_CALL(
+      test_observer,
+      OnAnimationTypeEnded(SidePanelAnimationCoordinator::AnimationType::kOpen))
+      .WillOnce([&animation_ended]() { animation_ended.SetValue(); });
+
+  EXPECT_CALL(test_observer, OnAnimationSequenceProgressed(_, _)).Times(0);
+  EXPECT_CALL(test_observer, OnAnimationSequenceEnded(_)).Times(0);
+
+  animation_coordinator->Start(
+      SidePanelAnimationCoordinator::AnimationType::kOpen);
+
+  EXPECT_TRUE(animation_started.Wait());
+  EXPECT_TRUE(animation_ended.Wait());
+
+  animation_started.Clear();
+  animation_ended.Clear();
+
+  EXPECT_CALL(test_observer,
+              OnAnimationTypeStarted(
+                  SidePanelAnimationCoordinator::AnimationType::kClose))
+      .WillOnce([&animation_started]() { animation_started.SetValue(); });
+
+  EXPECT_CALL(test_observer,
+              OnAnimationTypeEnded(
+                  SidePanelAnimationCoordinator::AnimationType::kClose))
+      .WillOnce([&animation_ended]() { animation_ended.SetValue(); });
+
+  EXPECT_CALL(test_observer, OnAnimationSequenceProgressed(_, _)).Times(0);
+  EXPECT_CALL(test_observer, OnAnimationSequenceEnded(_)).Times(0);
+
+  animation_coordinator->Start(
+      SidePanelAnimationCoordinator::AnimationType::kClose);
+
+  EXPECT_TRUE(animation_started.Wait());
+  EXPECT_TRUE(animation_ended.Wait());
+}
+
+IN_PROC_BROWSER_TEST_F(SidePanelAnimationCoordinatorBrowserTest,
+                       AnimationTypeObserversFireEndedWhenCanceled) {
+  SidePanelAnimationCoordinator* animation_coordinator =
+      GetAnimationCoordinator();
+
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestAnimationId);
+
+  // Add a test animation for open animation type.
+  AddAnimationSequence(SidePanelAnimationCoordinator::AnimationType::kOpen,
+                       {.animation_id = kTestAnimationId,
+                        .start = base::Milliseconds(0),
+                        .duration = base::Milliseconds(100)});
+
+  MockSidePanelAnimationObserver test_observer;
+  animation_coordinator->AddObserver(
+      SidePanelAnimationCoordinator::AnimationType::kOpen, &test_observer);
+
+  base::test::TestFuture<void> animation_started;
+  EXPECT_CALL(test_observer,
+              OnAnimationTypeStarted(
+                  SidePanelAnimationCoordinator::AnimationType::kOpen))
+      .WillOnce([&animation_started]() { animation_started.SetValue(); });
+
+  base::test::TestFuture<void> animation_canceled;
+  EXPECT_CALL(
+      test_observer,
+      OnAnimationTypeEnded(SidePanelAnimationCoordinator::AnimationType::kOpen))
+      .WillOnce([&animation_canceled]() { animation_canceled.SetValue(); });
+
+  animation_coordinator->Start(
+      SidePanelAnimationCoordinator::AnimationType::kOpen);
+
+  EXPECT_TRUE(animation_started.Wait());
+
+  animation_coordinator->Reset(
+      SidePanelAnimationCoordinator::AnimationType::kOpen);
+
+  EXPECT_TRUE(animation_canceled.Wait());
+}
+
+IN_PROC_BROWSER_TEST_F(SidePanelAnimationCoordinatorBrowserTest,
+                       AnimationsWithDifferentStartTimesFireInOrder) {
+  SidePanelAnimationCoordinator* animation_coordinator =
+      GetAnimationCoordinator();
+
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kFirstAnimationId);
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSecondAnimationId);
+
+  // Add two test animations for the open animation type with different start
+  // times.
+  AddAnimationSequence(SidePanelAnimationCoordinator::AnimationType::kOpen,
+                       {.animation_id = kFirstAnimationId,
+                        .start = base::Milliseconds(0),
+                        .duration = base::Milliseconds(50)});
+  AddAnimationSequence(SidePanelAnimationCoordinator::AnimationType::kOpen,
+                       {.animation_id = kSecondAnimationId,
+                        .start = base::Milliseconds(100),
+                        .duration = base::Milliseconds(50)});
+
+  MockSidePanelAnimationObserver first_observer;
+  MockSidePanelAnimationObserver second_observer;
+  animation_coordinator->AddObserver(kFirstAnimationId, &first_observer);
+  animation_coordinator->AddObserver(kSecondAnimationId, &second_observer);
+
+  // Set a custom container to control the animation time.
+  auto container = base::MakeRefCounted<gfx::AnimationContainer>();
+  animation_coordinator->animation_for_testing()->SetContainer(container.get());
+  gfx::AnimationContainerTestApi test_api(container.get());
+
+  animation_coordinator->Start(
+      SidePanelAnimationCoordinator::AnimationType::kOpen);
+
+  // Step 1: Advance by 25ms.
+  // Should trigger first_observer (0-50ms), but not second (100-150ms).
+  base::test::TestFuture<void> first_animation_progressed;
+  EXPECT_CALL(first_observer,
+              OnAnimationSequenceProgressed(kFirstAnimationId, _))
+      .Times(testing::AtLeast(1))
+      .WillOnce([&first_animation_progressed]() {
+        first_animation_progressed.SetValue();
+      });
+  EXPECT_CALL(second_observer,
+              OnAnimationSequenceProgressed(kSecondAnimationId, _))
+      .Times(0);
+
+  test_api.IncrementTime(base::Milliseconds(25));
+  EXPECT_TRUE(first_animation_progressed.Wait());
+
+  testing::Mock::VerifyAndClearExpectations(&first_observer);
+  testing::Mock::VerifyAndClearExpectations(&second_observer);
+
+  // Step 2: Advance by another 100ms (Total 125ms).
+  // First observer should be done (stopped at 50ms).
+  // Second observer should be active (100-150ms).
+  base::test::TestFuture<void> second_animation_progressed;
+  EXPECT_CALL(second_observer,
+              OnAnimationSequenceProgressed(kSecondAnimationId, _))
+      .Times(testing::AtLeast(1))
+      .WillOnce([&second_animation_progressed]() {
+        second_animation_progressed.SetValue();
+      });
+  EXPECT_CALL(first_observer,
+              OnAnimationSequenceProgressed(kFirstAnimationId, _))
+      .Times(0);
+
+  test_api.IncrementTime(base::Milliseconds(100));
+  EXPECT_TRUE(second_animation_progressed.Wait());
+}
+
+IN_PROC_BROWSER_TEST_F(SidePanelAnimationCoordinatorBrowserTest,
+                       CoordinatorUsesLinearTweenType) {
+  SidePanelAnimationCoordinator* animation_coordinator =
+      GetAnimationCoordinator();
+
+  // Set a custom container to control the animation time.
+  auto container = base::MakeRefCounted<gfx::AnimationContainer>();
+  animation_coordinator->animation_for_testing()->SetContainer(container.get());
+  gfx::AnimationContainerTestApi test_api(container.get());
+
+  animation_coordinator->Start(
+      SidePanelAnimationCoordinator::AnimationType::kOpen);
+
+  base::TimeDelta duration =
+      animation_coordinator->animation_spec_map_for_testing()
+          .at(SidePanelAnimationCoordinator::AnimationType::kOpen)
+          .GetAnimationDuration();
+
+  // Verify at 1%
+  test_api.IncrementTime(duration * 0.01);
+  EXPECT_EQ(animation_coordinator->animation_for_testing()->GetCurrentValue(),
+            0.01);
+
+  // Verify at 7% (Accumulated)
+  test_api.IncrementTime(duration * 0.06);
+  EXPECT_EQ(animation_coordinator->animation_for_testing()->GetCurrentValue(),
+            0.07);
+
+  // Verify at 17% (Accumulated)
+  test_api.IncrementTime(duration * 0.10);
+  EXPECT_EQ(animation_coordinator->animation_for_testing()->GetCurrentValue(),
+            0.17);
+
+  // Verify at 25% (Accumulated)
+  test_api.IncrementTime(duration * 0.08);
+  EXPECT_EQ(animation_coordinator->animation_for_testing()->GetCurrentValue(),
+            0.25);
+
+  // Verify at 31% (Accumulated)
+  test_api.IncrementTime(duration * 0.06);
+  EXPECT_EQ(animation_coordinator->animation_for_testing()->GetCurrentValue(),
+            0.31);
+
+  // Verify at 50% (Accumulated)
+  test_api.IncrementTime(duration * 0.19);
+  EXPECT_EQ(animation_coordinator->animation_for_testing()->GetCurrentValue(),
+            0.5);
+
+  // Verify at 63% (Accumulated)
+  test_api.IncrementTime(duration * 0.13);
+  EXPECT_EQ(animation_coordinator->animation_for_testing()->GetCurrentValue(),
+            0.63);
+
+  // Verify at 75% (Accumulated)
+  test_api.IncrementTime(duration * 0.12);
+  EXPECT_EQ(animation_coordinator->animation_for_testing()->GetCurrentValue(),
+            0.75);
+
+  // Verify at 95% (Accumulated)
+  test_api.IncrementTime(duration * 0.20);
+  EXPECT_EQ(animation_coordinator->animation_for_testing()->GetCurrentValue(),
+            0.95);
+
+  // Verify at 100% (Accumulated)
+  test_api.IncrementTime(duration * 0.05);
+  EXPECT_EQ(animation_coordinator->animation_for_testing()->GetCurrentValue(),
+            1.0);
 }
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar.cc b/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar.cc
index 192a6c0..3a1a743 100644
--- a/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar.cc
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar.cc
@@ -4,12 +4,16 @@
 
 #include "chrome/browser/ui/views/tab_sharing/tab_sharing_infobar.h"
 
+#include <algorithm>
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "base/functional/bind.h"
+#include "base/memory/raw_ptr.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/layout_constants.h"
+#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "chrome/browser/ui/views/screen_sharing_util.h"
@@ -25,6 +29,7 @@
 #include "ui/views/controls/button/md_text_button.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/link.h"
+#include "ui/views/layout/flex_layout_types.h"
 #include "ui/views/layout/layout_provider.h"
 #include "ui/views/style/platform_style.h"
 #include "ui/views/view_class_properties.h"
@@ -61,6 +66,14 @@
       CreateStatusMessageView(shared_tab_id, capturer_id, shared_tab_name,
                               capturer_name, role, capture_type));
 
+  if (base::FeatureList::IsEnabled(features::kInfobarRefresh)) {
+    status_message_view_->SetProperty(
+        views::kFlexBehaviorKey,
+        views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
+                                 views::MaximumFlexSizeRule::kPreferred)
+            .WithWeight(1));
+  }
+
   const int buttons = delegate_ptr->GetButtons();
   const auto create_button =
       [&](TabSharingInfoBarDelegate::TabSharingInfoBarButton type,
@@ -73,12 +86,20 @@
                 base::BindRepeating(click_function, base::Unretained(this)),
                 delegate_ptr->GetButtonLabel(type), button_context,
                 use_text_color_for_icon));
-        button->SetProperty(
-            views::kMarginsKey,
-            gfx::Insets::VH(ChromeLayoutProvider::Get()->GetDistanceMetric(
-                                DISTANCE_TOAST_CONTROL_VERTICAL),
-                            0));
 
+        if (base::FeatureList::IsEnabled(features::kInfobarRefresh)) {
+          button->SetCustomPadding(
+              gfx::Insets::VH(ChromeLayoutProvider::Get()->GetDistanceMetric(
+                                  DISTANCE_INFOBAR_BUTTON_VERTICAL_PADDING),
+                              ChromeLayoutProvider::Get()->GetDistanceMetric(
+                                  DISTANCE_INFOBAR_BUTTON_HORIZONTAL_PADDING)));
+        } else {
+          button->SetProperty(
+              views::kMarginsKey,
+              gfx::Insets::VH(ChromeLayoutProvider::Get()->GetDistanceMetric(
+                                  DISTANCE_TOAST_CONTROL_VERTICAL),
+                              0));
+        }
         const bool is_default_button =
             type == buttons || type == TabSharingInfoBarDelegate::kStop;
         button->SetStyle(is_default_button ? ui::ButtonStyle::kProminent
@@ -130,6 +151,13 @@
 TabSharingInfoBar::~TabSharingInfoBar() = default;
 
 void TabSharingInfoBar::Layout(PassKey) {
+  // If Refresh is enabled, InfoBarView uses a FlexLayout that handles centering
+  // automatically.
+  if (base::FeatureList::IsEnabled(features::kInfobarRefresh)) {
+    LayoutSuperclass<InfoBarView>(this);
+    return;
+  }
+
   LayoutSuperclass<InfoBarView>(this);
 
   if (stop_button_) {
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index c3cd83ad..15e5037 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -762,6 +762,16 @@
              ->is_collapsed();
 }
 
+std::optional<tab_groups::TabGroupId>
+BrowserTabStripController::GetFocusedGroup() const {
+  return model_->GetFocusedGroup();
+}
+
+void BrowserTabStripController::SetFocusedGroup(
+    std::optional<tab_groups::TabGroupId> group) {
+  model_->SetFocusedGroup(group);
+}
+
 void BrowserTabStripController::SetVisualDataForGroup(
     const tab_groups::TabGroupId& group,
     const tab_groups::TabGroupVisualData& visual_data) {
@@ -1072,6 +1082,10 @@
   }
 }
 
+void BrowserTabStripController::OnTabGroupFocusChanged(
+    std::optional<tab_groups::TabGroupId> new_group_id,
+    std::optional<tab_groups::TabGroupId> old_group_id) {}
+
 BrowserFrameView* BrowserTabStripController::GetFrameView() {
   return browser_view_->browser_widget()->GetFrameView();
 }
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
index 5334a1c..b2c6a9b 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
@@ -113,6 +113,9 @@
   TabGroup* GetTabGroup(const tab_groups::TabGroupId& group_id) const override;
   bool IsGroupCollapsed(const tab_groups::TabGroupId& group) const override;
 
+  std::optional<tab_groups::TabGroupId> GetFocusedGroup() const override;
+  void SetFocusedGroup(std::optional<tab_groups::TabGroupId> group) override;
+
   void SetVisualDataForGroup(
       const tab_groups::TabGroupId& group,
       const tab_groups::TabGroupVisualData& visual_data) override;
@@ -160,6 +163,9 @@
   void SetTabGroupNeedsAttention(const tab_groups::TabGroupId& group,
                                  bool attention) override;
   void OnSplitTabChanged(const SplitTabChange& change) override;
+  void OnTabGroupFocusChanged(
+      std::optional<tab_groups::TabGroupId> new_group_id,
+      std::optional<tab_groups::TabGroupId> old_group_id) override;
 
   const Browser* browser() const { return browser_view_->browser(); }
 
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller_browsertest.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller_browsertest.cc
index ea8e5b96..6190244 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller_browsertest.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller_browsertest.cc
@@ -383,3 +383,62 @@
   // Group should be collapsed
   EXPECT_TRUE(controller()->IsGroupCollapsed(group));
 }
+
+class BrowserTabStripControllerTestFocusedGroup
+    : public BrowserTabStripControllerTestBase {
+ public:
+  BrowserTabStripControllerTestFocusedGroup() {
+    scoped_feature_list_.InitAndEnableFeature(features::kTabGroupsFocusing);
+  }
+  ~BrowserTabStripControllerTestFocusedGroup() override = default;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(BrowserTabStripControllerTestFocusedGroup,
+                       SetAndGetFocusedGroup) {
+  // Create tabs and groups.
+  controller()->CreateNewTab(NewTabTypes::kNewTabCommand);
+  controller()->CreateNewTab(NewTabTypes::kNewTabCommand);
+  controller()->CreateNewTab(NewTabTypes::kNewTabCommand);
+  EXPECT_EQ(tab_strip_model()->count(), 4);
+
+  const tab_groups::TabGroupId group1 =
+      tab_strip_model()->AddToNewGroup({0, 1});
+  const tab_groups::TabGroupId group2 =
+      tab_strip_model()->AddToNewGroup({2, 3});
+
+  // Initially, no group is focused.
+  EXPECT_EQ(controller()->GetFocusedGroup(), std::nullopt);
+
+  // Focus on group1.
+  controller()->SetFocusedGroup(group1);
+  EXPECT_EQ(controller()->GetFocusedGroup(), group1);
+
+  // Focus on group2.
+  controller()->SetFocusedGroup(group2);
+  EXPECT_EQ(controller()->GetFocusedGroup(), group2);
+
+  // Unset focused group.
+  controller()->SetFocusedGroup(std::nullopt);
+  EXPECT_EQ(controller()->GetFocusedGroup(), std::nullopt);
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserTabStripControllerTestFocusedGroup,
+                       FocusedGroupIsResetWhenDeleted) {
+  // Create tabs and a group.
+  controller()->CreateNewTab(NewTabTypes::kNewTabCommand);
+  EXPECT_EQ(tab_strip_model()->count(), 2);
+  const tab_groups::TabGroupId group = tab_strip_model()->AddToNewGroup({0, 1});
+
+  // Focus on the group.
+  controller()->SetFocusedGroup(group);
+  EXPECT_EQ(controller()->GetFocusedGroup(), group);
+
+  // Delete the group by ungrouping all its tabs.
+  tab_strip_model()->RemoveFromGroup({0, 1});
+
+  // Verify the focused group is reset.
+  EXPECT_EQ(controller()->GetFocusedGroup(), std::nullopt);
+}
diff --git a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
index 97e318fe..c8e8b05 100644
--- a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
@@ -318,6 +318,16 @@
   return std::u16string();
 }
 
+std::optional<tab_groups::TabGroupId>
+FakeBaseTabStripController::GetFocusedGroup() const {
+  return focused_group_;
+}
+
+void FakeBaseTabStripController::SetFocusedGroup(
+    std::optional<tab_groups::TabGroupId> group) {
+  focused_group_ = group;
+}
+
 Profile* FakeBaseTabStripController::GetProfile() const {
   return nullptr;
 }
diff --git a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h
index b0e10e6..cc460ee 100644
--- a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h
@@ -107,6 +107,9 @@
   bool CanShowModalUI() const override;
   std::unique_ptr<ScopedTabStripModalUI> ShowModalUI() override;
 
+  std::optional<tab_groups::TabGroupId> GetFocusedGroup() const override;
+  void SetFocusedGroup(std::optional<tab_groups::TabGroupId> group) override;
+
 #if BUILDFLAG(IS_CHROMEOS)
   bool IsLockedForOnTask() override;
 
@@ -131,6 +134,7 @@
   tab_groups::TabGroupVisualData fake_group_data_;
   std::vector<std::optional<tab_groups::TabGroupId>> tab_groups_;
 
+  std::optional<tab_groups::TabGroupId> focused_group_;
   ui::ListSelectionModel selection_model_;
 };
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip_action_container_unittest.cc b/chrome/browser/ui/views/tabs/tab_strip_action_container_unittest.cc
index 4842684..96bd3411 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_action_container_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_action_container_unittest.cc
@@ -40,14 +40,11 @@
 #include "ui/views/test/views_test_utils.h"
 #include "ui/views/widget/widget.h"
 
-static_assert(BUILDFLAG(ENABLE_GLIC));
-
-// TODO(crbug.com/461140208): Re-enable failing tests on ChromeOS.
 #if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE(test_name) DISABLED_##test_name
-#else
-#define MAYBE(test_name) test_name
-#endif
+#include "chrome/browser/ash/test/glic_user_session_test_helper.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
+static_assert(BUILDFLAG(ENABLE_GLIC));
 
 namespace {
 using testing::SizeIs;
@@ -122,6 +119,10 @@
     testing_profile_manager_ = std::make_unique<TestingProfileManager>(
         TestingBrowserProcess::GetGlobal());
     ASSERT_TRUE(testing_profile_manager_->SetUp());
+#if BUILDFLAG(IS_CHROMEOS)
+    glic_user_session_test_helper_.PreProfileSetUp(
+        testing_profile_manager_->profile_manager());
+#endif  // BUILDFLAG(IS_CHROMEOS)
     TestingBrowserProcess::GetGlobal()->CreateGlobalFeaturesForTesting();
     ChromeViewsTestBase::SetUp();
     profile_ = testing_profile_manager_->CreateTestingProfile(
@@ -146,6 +147,9 @@
     ChromeViewsTestBase::TearDown();
     TestingBrowserProcess::GetGlobal()->GetFeatures()->Shutdown();
     testing_profile_manager_.reset();
+#if BUILDFLAG(IS_CHROMEOS)
+    glic_user_session_test_helper_.PostProfileTearDown();
+#endif  // BUILDFLAG(IS_CHROMEOS)
   }
 
   void BuildGlicContainer(bool use_otr_profile) {
@@ -223,6 +227,9 @@
   // Owned by TabStrip.
 
   content::RenderViewHostTestEnabler render_view_host_test_enabler_;
+#if BUILDFLAG(IS_CHROMEOS)
+  ash::GlicUserSessionTestHelper glic_user_session_test_helper_;
+#endif  // BUILDFLAG(IS_CHROMEOS)
   raw_ptr<TestingProfile> profile_ = nullptr;
   std::unique_ptr<content::WebContents> web_contents_;
   gfx::AnimationTestApi::RenderModeResetter animation_mode_reset_;
@@ -235,18 +242,17 @@
                          ::testing::Bool(),
                          &TabStripActionContainerTest::GetParamName);
 
-TEST_P(TabStripActionContainerTest, MAYBE(GlicButtonDrawing)) {
+TEST_P(TabStripActionContainerTest, GlicButtonDrawing) {
   BuildGlicContainer(/*use_otr_profile=*/false);
   EXPECT_TRUE(tab_strip_action_container_->GetGlicButton());
 }
 
-TEST_P(TabStripActionContainerTest, MAYBE(GlicButtonUnsupportedProfile)) {
+TEST_P(TabStripActionContainerTest, GlicButtonUnsupportedProfile) {
   BuildGlicContainer(/*use_otr_profile=*/true);
   EXPECT_FALSE(tab_strip_action_container_->GetGlicButton());
 }
 
-TEST_P(TabStripActionContainerTest,
-       MAYBE(OrdersButtonsCorrectlyAtConstruction)) {
+TEST_P(TabStripActionContainerTest, OrdersButtonsCorrectlyAtConstruction) {
   BuildGlicContainer(/*use_otr_profile=*/false);
   ASSERT_EQ(tab_strip_action_container_->tab_declutter_button(),
             tab_strip_action_container_->children()[0]);
@@ -274,7 +280,7 @@
 #endif  // !BUILDFLAG(IS_MAC)
 }
 
-TEST_P(TabStripActionContainerTest, MAYBE(OrdersButtonsCorrectlyWhenShown)) {
+TEST_P(TabStripActionContainerTest, OrdersButtonsCorrectlyWhenShown) {
   BuildGlicContainer(/*use_otr_profile=*/false);
 
 // TODO(crbug.com/437141881): Fix flaky tests on Mac.
@@ -316,7 +322,7 @@
 #endif  // !BUILDFLAG(IS_MAC)
 }
 
-TEST_P(TabStripActionContainerTest, MAYBE(GlicButtonUpdateLabel)) {
+TEST_P(TabStripActionContainerTest, GlicButtonUpdateLabel) {
   BuildGlicContainer(/*use_otr_profile=*/false);
   glic_nudge_controller_->UpdateNudgeLabel(
       web_contents(), "TEST", /*prompt_suggestion=*/std::nullopt,
@@ -324,7 +330,7 @@
   ASSERT_EQ(tab_strip_action_container_->GetGlicButton()->GetText(), u"TEST");
 }
 
-TEST_P(TabStripActionContainerTest, MAYBE(GlicButtonHideNudgeOnTabChange)) {
+TEST_P(TabStripActionContainerTest, GlicButtonHideNudgeOnTabChange) {
   BuildGlicContainer(/*use_otr_profile=*/false);
   glic_nudge_controller_->SetDelegate(tab_strip_action_container_.get());
 
@@ -358,7 +364,7 @@
                          ::testing::Bool(),
                          &TabStripActionContainerTest::GetParamName);
 
-TEST_P(TabStripActionContainerTestWithProduct, MAYBE(OrdersButtonsCorrectly)) {
+TEST_P(TabStripActionContainerTestWithProduct, OrdersButtonsCorrectly) {
   BuildGlicContainer(/*use_otr_profile=*/false);
 
   ASSERT_EQ(tab_strip_action_container_->tab_declutter_button(),
diff --git a/chrome/browser/ui/views/tabs/tab_strip_controller.h b/chrome/browser/ui/views/tabs/tab_strip_controller.h
index 80dc6fd0e..56c1e2a 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_strip_controller.h
@@ -194,6 +194,13 @@
   // exist or is not collapsed.
   virtual bool IsGroupCollapsed(const tab_groups::TabGroupId& group) const = 0;
 
+  // Returns the ID of the group that is focused. If no group is focused,
+  // returns nullopt.
+  virtual std::optional<tab_groups::TabGroupId> GetFocusedGroup() const = 0;
+
+  // Sets the group to be focused.
+  virtual void SetFocusedGroup(std::optional<tab_groups::TabGroupId> group) = 0;
+
   // Sets the title and color ID of the given `group`.
   virtual void SetVisualDataForGroup(
       const tab_groups::TabGroupId& group,
diff --git a/chrome/browser/ui/views/user_education/browser_user_education_service.cc b/chrome/browser/ui/views/user_education/browser_user_education_service.cc
index f6ce32e..5327bdb 100644
--- a/chrome/browser/ui/views/user_education/browser_user_education_service.cc
+++ b/chrome/browser/ui/views/user_education/browser_user_education_service.cc
@@ -252,10 +252,9 @@
   using user_education::FeaturePromoSpecification;
   using user_education::HelpBubbleArrow;
   using user_education::Metadata;
-  using AdditionalCondition = user_education::FeaturePromoSpecification::
-      AdditionalConditions::AdditionalCondition;
-  using AdditionalConditions =
-      user_education::FeaturePromoSpecification::AdditionalConditions;
+  using AdditionalCondition =
+      FeaturePromoSpecification::AdditionalConditions::AdditionalCondition;
+  using AdditionalConditions = FeaturePromoSpecification::AdditionalConditions;
 
   // This icon got updated, so select the 2023 Refresh version.
   // Note that the WebUI refresh state is not taken into account, so
@@ -516,7 +515,7 @@
 
   // kIPHDesktopPwaInstallFeature:
   registry.RegisterFeature(
-      std::move(user_education::FeaturePromoSpecification::CreateForLegacyPromo(
+      std::move(FeaturePromoSpecification::CreateForLegacyPromo(
                     &feature_engagement::kIPHDesktopPwaInstallFeature,
                     kInstallPwaElementId, IDS_DESKTOP_PWA_INSTALL_PROMO)
                     .SetMetadata(89, "phillis@chromium.org",
@@ -596,7 +595,7 @@
 #if BUILDFLAG(ENABLE_EXTENSIONS)
   // kIPHExtensionsMenuFeature:
   registry.RegisterFeature(std::move(
-      user_education::FeaturePromoSpecification::CreateForSnoozePromo(
+      FeaturePromoSpecification::CreateForSnoozePromo(
           feature_engagement::kIPHExtensionsMenuFeature,
           kExtensionsMenuButtonElementId,
           IDS_EXTENSIONS_MENU_IPH_ENTRY_POINT_BODY)
@@ -607,7 +606,7 @@
 
   // kIPHExtensionsRequestAccessButtonFeature
   registry.RegisterFeature(std::move(
-      user_education::FeaturePromoSpecification::CreateForSnoozePromo(
+      FeaturePromoSpecification::CreateForSnoozePromo(
           feature_engagement::kIPHExtensionsRequestAccessButtonFeature,
           kExtensionsRequestAccessButtonElementId,
           IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_IPH_ENTRY_POINT_BODY)
@@ -618,14 +617,14 @@
                        "requests access permission.")));
 
   // kIPHExtensionsZeroStatePromoFeature
-  user_education::Metadata kIPHExtensionsZeroStatePromoFeatureMetaData =
-      user_education::Metadata(140, "uwyiming@google.com",
-                               "Triggered when a user has no "
-                               "extensions installed.");
+  Metadata iph_extensions_zero_state_promo_feature_metaData(
+      140, "uwyiming@google.com",
+      "Triggered when a user has no "
+      "extensions installed.");
   switch (feature_engagement::kIPHExtensionsZeroStatePromoVariantParam.Get()) {
     case feature_engagement::kCustomActionIph:
       registry.RegisterFeature(std::move(
-          user_education::FeaturePromoSpecification::CreateForCustomAction(
+          FeaturePromoSpecification::CreateForCustomAction(
               feature_engagement::kIPHExtensionsZeroStatePromoFeature,
               kToolbarAppMenuButtonElementId,
               IDS_EXTENSIONS_ZERO_STATE_PROMO_CUSTOM_ACTION_IPH_DESCRIPTION,
@@ -636,7 +635,7 @@
               .SetCustomActionIsDefault(true)
               .SetBubbleTitleText(IDS_EXTENSIONS_ZERO_STATE_PROMO_IPH_TITLE)
               .SetMetadata(
-                  std::move(kIPHExtensionsZeroStatePromoFeatureMetaData))
+                  std::move(iph_extensions_zero_state_promo_feature_metaData))
               .SetHighlightedMenuItem(
                   ExtensionsMenuModel::kVisitChromeWebStoreMenuItem)));
       break;
@@ -645,7 +644,7 @@
     case feature_engagement::kCustomUiChipIphV3:
     case feature_engagement::kCustomUIPlainLinkIph:
       registry.RegisterFeature(std::move(
-          user_education::FeaturePromoSpecification::CreateForCustomUi(
+          FeaturePromoSpecification::CreateForCustomUi(
               feature_engagement::kIPHExtensionsZeroStatePromoFeature,
               kToolbarAppMenuButtonElementId,
               MakeCustomWebUIHelpBubbleFactoryCallback<
@@ -655,7 +654,7 @@
               // actual actions.
               base::DoNothing())
               .SetMetadata(
-                  std::move(kIPHExtensionsZeroStatePromoFeatureMetaData))
+                  std::move(iph_extensions_zero_state_promo_feature_metaData))
               .SetBubbleArrow(HelpBubbleArrow::kTopRight)
               .SetHighlightedMenuItem(
                   ExtensionsMenuModel::kVisitChromeWebStoreMenuItem)));
@@ -904,8 +903,8 @@
           IDS_SIGNIN_DICE_WEB_INTERCEPT_BUBBLE_CHROME_SIGNIN_IPH_TEXT_SIGNIN_REMINDER,
           IDS_SIGNIN_DICE_WEB_INTERCEPT_BUBBLE_CHROME_SIGNIN_IPH_TEXT_SIGNIN_REMINDER_SCREENREADER,
           FeaturePromoSpecification::AcceleratorInfo(IDC_SHOW_AVATAR_MENU))
-          .SetPromoSubtype(user_education::FeaturePromoSpecification::
-                               PromoSubtype::kKeyedNotice)
+          .SetPromoSubtype(
+              FeaturePromoSpecification::PromoSubtype::kKeyedNotice)
           .SetBubbleTitleText(
               IDS_SIGNIN_DICE_WEB_INTERCEPT_BUBBLE_CHROME_SIGNIN_IPH_TITLE_SIGNIN_REMINDER)
           .SetBubbleArrow(HelpBubbleArrow::kTopRight)
@@ -920,8 +919,8 @@
           kNotificationContentSettingImageView, IDS_QUIET_NOTIFICATION_IPH_TEXT,
           IDS_QUIET_NOTIFICATION_IPH_TEXT_SCREENREADER,
           FeaturePromoSpecification::AcceleratorInfo())
-          .SetPromoSubtype(user_education::FeaturePromoSpecification::
-                               PromoSubtype::kKeyedNotice)
+          .SetPromoSubtype(
+              FeaturePromoSpecification::PromoSubtype::kKeyedNotice)
           .SetBubbleArrow(HelpBubbleArrow::kTopRight)
           .SetReshowPolicy(base::Days(100), /*max_show_count=*/5)
           .SetMetadata(80, "lyf@chromium.org",
@@ -1221,8 +1220,8 @@
                     "SupervisedUserProfileSignIn_IPHPromo_"
                     "ParentalControlsPageOpened"));
               }))
-          .SetPromoSubtype(user_education::FeaturePromoSpecification::
-                               PromoSubtype::kActionableAlert)
+          .SetPromoSubtype(
+              FeaturePromoSpecification::PromoSubtype::kActionableAlert)
           .SetBubbleIcon(&vector_icons::kFamilyLinkIcon)
           .SetBubbleTitleText(IDS_SUPERVISED_USER_PROFILE_SIGNIN_IPH_TITLE)
           .SetBubbleArrow(HelpBubbleArrow::kTopRight)
@@ -1244,8 +1243,8 @@
                 ShowSingletonTab(GetBrowser(ctx),
                                  GURL(chrome::kChromeUIAccountSettingsURL));
               }))
-          .SetPromoSubtype(user_education::FeaturePromoSpecification::
-                               PromoSubtype::kActionableAlert)
+          .SetPromoSubtype(
+              FeaturePromoSpecification::PromoSubtype::kActionableAlert)
           .SetBubbleArrow(HelpBubbleArrow::kTopRight)
           .SetCustomActionIsDefault(false)
           .SetMetadata(142, "ddac@google.com",
@@ -1269,7 +1268,7 @@
       std::move(FeaturePromoSpecification::CreateForLegacyPromo(
                     &feature_engagement::kIPHTabSearchFeature,
                     kTabSearchButtonElementId, IDS_TAB_SEARCH_PROMO)
-                    .SetBubbleArrow(user_education::HelpBubbleArrow::kTopLeft)
+                    .SetBubbleArrow(HelpBubbleArrow::kTopLeft)
                     .SetMetadata(92, "tluk@chromium.org",
                                  "Triggered once when there are more than 8 "
                                  "tabs in the tab strip.")));
@@ -1281,7 +1280,7 @@
           kTabSearchButtonElementId, IDS_TAB_SEARCH_TOOLBAR_BUTTON_PROMO_BODY,
           IDS_TAB_SEARCH_TOOLBAR_BUTTON_PROMO_BODY,
           FeaturePromoSpecification::AcceleratorInfo())
-          .SetBubbleArrow(user_education::HelpBubbleArrow::kTopRight)
+          .SetBubbleArrow(HelpBubbleArrow::kTopRight)
           .SetBubbleIcon(kLightbulbOutlineIcon)
           .SetBubbleTitleText(IDS_TAB_SEARCH_TOOLBAR_BUTTON_PROMO_TITLE)
           .SetMetadata(136, "emshack@chromium.org",
@@ -1371,8 +1370,7 @@
                 params.target_url =
                     chrome::GetSettingsUrl(chrome::kPerformanceSubPage);
                 params.bubble_anchor_id = kInactiveTabSettingElementId;
-                params.bubble_arrow =
-                    user_education::HelpBubbleArrow::kBottomRight;
+                params.bubble_arrow = HelpBubbleArrow::kBottomRight;
                 params.bubble_text =
                     l10n_util::GetStringUTF16(IDS_DISCARD_RING_SETTINGS_TOAST);
                 params.close_button_alt_text_id = IDS_CLOSE_PROMO;
@@ -1504,8 +1502,8 @@
                 }
               }))
           .SetBubbleArrow(HelpBubbleArrow::kTopRight)
-          .SetPromoSubtype(user_education::FeaturePromoSpecification::
-                               PromoSubtype::kKeyedNotice)
+          .SetPromoSubtype(
+              FeaturePromoSpecification::PromoSubtype::kKeyedNotice)
           .SetMetadata(122, "dibyapal@chromium.org",
                        "Triggered once per-app when a link is captured and "
                        "opened in a PWA.")));
@@ -1543,8 +1541,8 @@
                 }
               }))
           .SetBubbleArrow(HelpBubbleArrow::kTopLeft)
-          .SetPromoSubtype(user_education::FeaturePromoSpecification::
-                               PromoSubtype::kKeyedNotice)
+          .SetPromoSubtype(
+              FeaturePromoSpecification::PromoSubtype::kKeyedNotice)
           .SetMetadata(122, "finnur@chromium.org",
                        "Triggered once per-app when a link is captured and "
                        "opened in a browser tab.")));
@@ -1571,7 +1569,7 @@
   if (MobilePromoOnDesktopTypeEnabled(
           MobilePromoOnDesktopPromoType::kLensPromo)) {
     registry.RegisterFeature(
-        std::move(user_education::FeaturePromoSpecification::CreateForCustomUi(
+        std::move(FeaturePromoSpecification::CreateForCustomUi(
                       feature_engagement::kIPHiOSLensPromoDesktopFeature,
                       kSidePanelElementId,
                       user_education::CreateCustomHelpBubbleViewFactoryCallback(
@@ -1587,15 +1585,15 @@
   if (MobilePromoOnDesktopTypeEnabled(
           MobilePromoOnDesktopPromoType::kESBPromo)) {
     registry.RegisterFeature(std::move(
-        user_education::FeaturePromoSpecification::CreateForCustomUi(
+        FeaturePromoSpecification::CreateForCustomUi(
             feature_engagement::kIPHiOSEnhancedBrowsingDesktopFeature,
             kToolbarAppMenuButtonElementId,
             user_education::CreateCustomHelpBubbleViewFactoryCallback(
                 base::BindRepeating(
                     &IOSPromoBubbleView::Create,
                     desktop_to_mobile_promos::PromoType::kEnhancedBrowsing)))
-            .SetPromoSubtype(user_education::FeaturePromoSpecification::
-                                 PromoSubtype::kActionableAlert)
+            .SetPromoSubtype(
+                FeaturePromoSpecification::PromoSubtype::kActionableAlert)
             .SetMetadata(144, "scottyoder@google.com",
                          "Triggered when ESB is first enabled.")));
   }
@@ -1604,7 +1602,7 @@
   if (MobilePromoOnDesktopTypeEnabled(
           MobilePromoOnDesktopPromoType::kAutofillPromo)) {
     registry.RegisterFeature(
-        std::move(user_education::FeaturePromoSpecification::CreateForCustomUi(
+        std::move(FeaturePromoSpecification::CreateForCustomUi(
                       feature_engagement::kIPHiOSPasswordPromoDesktopFeature,
                       kPasswordsOmniboxKeyIconElementId,
                       user_education::CreateCustomHelpBubbleViewFactoryCallback(
@@ -1766,7 +1764,7 @@
     auto password_manager_tutorial =
         TutorialDescription::Create<kPasswordManagerTutorialMetricPrefix>(
             // Bubble step - Browser app menu
-            TutorialDescription::BubbleStep(kToolbarAppMenuButtonElementId)
+            BubbleStep(kToolbarAppMenuButtonElementId)
                 .SetBubbleBodyText(IDS_TUTORIAL_PASSWORD_MANAGER_OPEN_APP_MENU)
                 .SetBubbleArrow(HelpBubbleArrow::kTopRight),
 
@@ -1779,46 +1777,41 @@
             TutorialDescription::If(AppMenuModel::kPasswordAndAutofillMenuItem)
                 .Then(
                     // Bubble step - Passwords and Autofill sub menu item
-                    TutorialDescription::BubbleStep(
-                        AppMenuModel::kPasswordAndAutofillMenuItem)
+                    BubbleStep(AppMenuModel::kPasswordAndAutofillMenuItem)
                         .SetBubbleBodyText(
                             IDS_TUTORIAL_PASSWORD_MANAGER_CLICK_PASSWORDS_MENU)
                         .SetBubbleArrow(HelpBubbleArrow::kRightCenter)),
 
             // Bubble step - "Password Manager" menu item
-            TutorialDescription::BubbleStep(
-                AppMenuModel::kPasswordManagerMenuItem)
+            BubbleStep(AppMenuModel::kPasswordManagerMenuItem)
                 .SetBubbleBodyText(
                     IDS_TUTORIAL_PASSWORD_MANAGER_CLICK_PASSWORD_MANAGER)
                 .SetBubbleArrow(HelpBubbleArrow::kRightCenter)
                 .AbortIfVisibilityLost(false),
 
             // Bubble step - "Add shortcut" row
-            TutorialDescription::BubbleStep(
-                PasswordManagerUI::kAddShortcutElementId)
+            BubbleStep(PasswordManagerUI::kAddShortcutElementId)
                 .SetBubbleBodyText(IDS_TUTORIAL_PASSWORD_MANAGER_ADD_SHORTCUT)
                 .SetBubbleArrow(HelpBubbleArrow::kTopCenter)
                 .InAnyContext(),
 
             // Event step - Click on "Add shortcut"
-            TutorialDescription::EventStep(
-                PasswordManagerUI::kAddShortcutCustomEventId)
+            EventStep(PasswordManagerUI::kAddShortcutCustomEventId)
                 .InSameContext(),
 
             // Bubble step - "Install" row
-            TutorialDescription::BubbleStep(
-                web_app::WebAppInstallDialogDelegate::
-                    kPwaInstallDialogInstallButton)
+            BubbleStep(web_app::WebAppInstallDialogDelegate::
+                           kPwaInstallDialogInstallButton)
                 .SetBubbleBodyText(IDS_TUTORIAL_PASSWORD_MANAGER_CLICK_INSTALL)
                 .SetBubbleArrow(HelpBubbleArrow::kTopRight),
 
             // Event step - Click on "Install"
-            TutorialDescription::EventStep(
+            EventStep(
                 web_app::WebAppInstallDialogDelegate::kInstalledPWAEventId)
                 .InSameContext(),
 
             // Completion of the tutorial.
-            TutorialDescription::BubbleStep(kTopContainerElementId)
+            BubbleStep(kTopContainerElementId)
                 .SetBubbleTitleText(IDS_TUTORIAL_GENERIC_SUCCESS_TITLE)
                 .SetBubbleBodyText(IDS_TUTORIAL_PASSWORD_MANAGER_SUCCESS_BODY)
                 .SetBubbleArrow(HelpBubbleArrow::kNone));
@@ -1837,12 +1830,12 @@
         TutorialDescription::Create<kLensOverlayTutorialMetricPrefix>(
 
             // Bubble step - Address bar
-            TutorialDescription::BubbleStep(kOmniboxElementId)
+            BubbleStep(kOmniboxElementId)
                 .SetBubbleBodyText(IDS_TUTORIAL_LENS_OVERLAY_CLICK_ADDRESS_BAR)
                 .SetBubbleArrow(HelpBubbleArrow::kTopCenter),
 
             // Bubble step - Lens button
-            TutorialDescription::BubbleStep(kLensOverlayPageActionIconElementId)
+            BubbleStep(kLensOverlayPageActionIconElementId)
                 .SetBubbleBodyText(
                     IDS_TUTORIAL_LENS_OVERLAY_HOMEWORK_CLICK_LENS)
                 .SetBubbleArrow(HelpBubbleArrow::kTopRight),
@@ -1851,7 +1844,7 @@
             HiddenStep::WaitForHidden(kLensOverlayPageActionIconElementId),
 
             // Completion of the tutorial after side panel appears.
-            TutorialDescription::BubbleStep(kLensSidePanelSearchBoxElementId)
+            BubbleStep(kLensSidePanelSearchBoxElementId)
                 .SetBubbleTitleText(IDS_TUTORIAL_GENERIC_SUCCESS_TITLE)
                 .SetBubbleBodyText(IDS_TUTORIAL_LENS_OVERLAY_CLICK_SEARCH_BOX)
                 .SetBubbleArrow(HelpBubbleArrow::kLeftTop)
@@ -1893,15 +1886,14 @@
                     })),
 
             // Bubble step - inactive tab to right click.
-            TutorialDescription::BubbleStep(kLastInactiveTabElementName)
+            BubbleStep(kLastInactiveTabElementName)
                 .SetBubbleBodyText(IDS_SPLIT_VIEW_TAB_SWITCH_STEP_IPH_BODY)
                 .SetBubbleArrow(HelpBubbleArrow::kTopLeft),
 
             HiddenStep::WaitForShown(kToolbarSplitTabsToolbarButtonElementId),
 
             // Bubble step - highlight the toolbar button.
-            TutorialDescription::BubbleStep(
-                kToolbarSplitTabsToolbarButtonElementId)
+            BubbleStep(kToolbarSplitTabsToolbarButtonElementId)
                 .SetBubbleBodyText(IDS_SPLIT_VIEW_TOOLBAR_BUTTON_STEP_IPH_BODY)
                 .SetBubbleArrow(HelpBubbleArrow::kTopLeft),
 
@@ -1909,8 +1901,7 @@
                 SplitTabMenuModel::kReversePositionMenuItem),
 
             // Completion of the tutorial after split view appears.
-            TutorialDescription::BubbleStep(
-                SplitTabMenuModel::kExitSplitMenuItem)
+            BubbleStep(SplitTabMenuModel::kExitSplitMenuItem)
                 .SetBubbleTitleText(IDS_TUTORIAL_GENERIC_SUCCESS_TITLE)
                 .SetBubbleBodyText(
                     IDS_SPLIT_VIEW_TAB_SWITCH_COMPLETION_IPH_BODY)
diff --git a/chrome/browser/ui/webui/history/browsing_history_handler_unittest.cc b/chrome/browser/ui/webui/history/browsing_history_handler_unittest.cc
index 8ed42e02..4253c4f 100644
--- a/chrome/browser/ui/webui/history/browsing_history_handler_unittest.cc
+++ b/chrome/browser/ui/webui/history/browsing_history_handler_unittest.cc
@@ -359,10 +359,12 @@
   EXPECT_CALL(*mock_page(), SendAccountInfo(_));
   // Update the account info with all the necessary fields for
   // AccountInfo::isValid() to be true.
-  account_info.hosted_domain = "example.com";
-  account_info.full_name = "Test User";
-  account_info.given_name = "Test";
-  account_info.picture_url = "http://example.com/test.jpg";
+  account_info = AccountInfo::Builder(account_info)
+                     .SetFullName("Test User")
+                     .SetGivenName("Test")
+                     .SetHostedDomain("example.com")
+                     .SetAvatarUrl("http://example.com/test.jpg")
+                     .Build();
   ASSERT_TRUE(account_info.IsValid());
 
   signin::UpdateAccountInfoForAccount(identity_manager, account_info);
diff --git a/chrome/browser/ui/webui/new_tab_page/action_chips/action_chips_generator.cc b/chrome/browser/ui/webui/new_tab_page/action_chips/action_chips_generator.cc
index 37828e69..09f02b60 100644
--- a/chrome/browser/ui/webui/new_tab_page/action_chips/action_chips_generator.cc
+++ b/chrome/browser/ui/webui/new_tab_page/action_chips/action_chips_generator.cc
@@ -44,7 +44,10 @@
     return ChipsGenerationScenario::kStaticChipsOnly;
   }
   // TODO: b:457512149 - Support the deep dive chips when the EDU tab check is
-  // in.
+  // in. For now, the param is used to enable the deep dive chips.
+  if (ntp_features::kNtpNextShowDeepDiveSuggestionsParam.Get()) {
+    return ChipsGenerationScenario::kDeepDive;
+  }
   return ChipsGenerationScenario::kSteady;
 }
 
@@ -72,6 +75,14 @@
   return chip;
 }
 
+ActionChipPtr CreateDeepDiveChip(std::string_view suggestion) {
+  ActionChipPtr chip = ActionChip::New();
+  chip->type = ChipType::kDeepDive;
+  chip->title = "Deep dive";
+  chip->suggestion = suggestion;
+  return chip;
+}
+
 ActionChipPtr CreateImageCreationChip(std::string_view suggestion) {
   ActionChipPtr chip = ActionChip::New();
   chip->type = ChipType::kImage;
@@ -131,6 +142,18 @@
           tab.has_value() ? CreateTabInfo(*tab_id_generator_, *tab) : nullptr,
           /*options=*/{}));
       return;
+    case ChipsGenerationScenario::kDeepDive: {
+      std::vector<ActionChipPtr> chips;
+      if (tab.has_value()) {
+        chips.push_back(CreateRecentTabChip(
+            CreateTabInfo(*tab_id_generator_, *tab), /*suggestion=*/""));
+        // TODO: b:457512149 - Use suggestions from the server side.
+        chips.push_back(CreateDeepDiveChip("Test suggestion 1"));
+        chips.push_back(CreateDeepDiveChip("Test suggestion 2"));
+      }
+      std::move(callback).Run(std::move(chips));
+      return;
+    }
     default:
       // TODO: b:457512149 - handle the other cases correctly.
       std::move(callback).Run(CreateChipsForSteadyState(
diff --git a/chrome/browser/ui/webui/new_tab_page/action_chips/action_chips_generator_unittest.cc b/chrome/browser/ui/webui/new_tab_page/action_chips/action_chips_generator_unittest.cc
index 6ed496ce..94568ce 100644
--- a/chrome/browser/ui/webui/new_tab_page/action_chips/action_chips_generator_unittest.cc
+++ b/chrome/browser/ui/webui/new_tab_page/action_chips/action_chips_generator_unittest.cc
@@ -75,6 +75,11 @@
   return *kInstance;
 }
 
+ActionChip CreateStaticDeepDiveChip(std::string_view suggestion) {
+  return ActionChip("Deep dive", std::string(suggestion), ChipType::kDeepDive,
+                    nullptr);
+}
+
 // A container to store WebContents and its dependency.
 // The main usage is to populate TabInterface.
 struct TabFixture {
@@ -170,4 +175,40 @@
                   Pointee(Eq(std::cref(GetStaticImageGenerationChip())))));
 }
 
+TEST(ActionChipGeneratorTest,
+     GenerateDeepDiveChipsWhenNtpNextShowDeepDiveSuggestionsParamIsTrue) {
+  const GURL page_url("https://google.com/");
+  const std::u16string page_title(u"Google");
+  std::unique_ptr<TabFixture> tab_fixture =
+      CreateTabFixture(page_url, page_title);
+  tabs::MockTabInterface mock_tab;
+  EXPECT_CALL(mock_tab, GetContents())
+      .WillRepeatedly(Return(tab_fixture->web_contents.get()));
+
+  base::test::ScopedFeatureList list;
+  list.InitAndEnableFeatureWithParameters(
+      ntp_features::kNtpNextFeatures,
+      {{ntp_features::kNtpNextShowDeepDiveSuggestionsParam.name, "true"}});
+
+  base::RunLoop run_loop;
+  std::vector<ActionChipPtr> actual;
+  CreateActionChipsGenerator().GenerateActionChips(
+      static_cast<const TabInterface*>(&mock_tab),
+      base::BindLambdaForTesting(
+          [&run_loop, &actual](std::vector<ActionChipPtr> chips) {
+            actual = std::move(chips);
+            run_loop.Quit();
+          }));
+  run_loop.Run();
+  ActionChip most_recent_tab_chip = CreateStaticRecentTabChip(
+      TabInfo::New(GetTabHandleId(&mock_tab), base::UTF16ToUTF8(page_title),
+                   page_url, base::Time::FromMillisecondsSinceUnixEpoch(0)));
+  ActionChip deep_dive_chip_1 = CreateStaticDeepDiveChip("Test suggestion 1");
+  ActionChip deep_dive_chip_2 = CreateStaticDeepDiveChip("Test suggestion 2");
+
+  EXPECT_THAT(actual, ElementsAre(Pointee(Eq(std::cref(most_recent_tab_chip))),
+                                  Pointee(Eq(std::cref(deep_dive_chip_1))),
+                                  Pointee(Eq(std::cref(deep_dive_chip_2)))));
+}
+
 }  // namespace
diff --git a/chrome/browser/ui/webui/new_tab_page/composebox/variations/composebox_fieldtrial.cc b/chrome/browser/ui/webui/new_tab_page/composebox/variations/composebox_fieldtrial.cc
index 530909a..bd44031 100644
--- a/chrome/browser/ui/webui/new_tab_page/composebox/variations/composebox_fieldtrial.cc
+++ b/chrome/browser/ui/webui/new_tab_page/composebox/variations/composebox_fieldtrial.cc
@@ -58,8 +58,10 @@
   image_upload->set_downscale_max_image_width(1600);
   image_upload->set_downscale_max_image_height(1600);
   image_upload->set_image_compression_quality(40);
-  image_upload->set_mime_types_allowed("image/*");
-
+  // The current list of image types that Lens Backend supports
+  image_upload->set_mime_types_allowed(
+    "image/avif,image/bmp,image/jpeg,image/png,image/webp,image/heif,"
+    "image/heic");
   auto* attachment_upload = composebox->mutable_attachment_upload();
   attachment_upload->set_max_size_bytes(200000000);
   attachment_upload->set_mime_types_allowed(".pdf,application/pdf");
diff --git a/chrome/browser/ui/webui/new_tab_page/composebox/variations/composebox_fieldtrial_config_unittest.cc b/chrome/browser/ui/webui/new_tab_page/composebox/variations/composebox_fieldtrial_config_unittest.cc
index 083296b3..7bac12a0 100644
--- a/chrome/browser/ui/webui/new_tab_page/composebox/variations/composebox_fieldtrial_config_unittest.cc
+++ b/chrome/browser/ui/webui/new_tab_page/composebox/variations/composebox_fieldtrial_config_unittest.cc
@@ -72,7 +72,9 @@
   EXPECT_EQ(image_upload.downscale_max_image_width(), 1600);
   EXPECT_EQ(image_upload.downscale_max_image_height(), 1600);
   EXPECT_EQ(image_upload.image_compression_quality(), 40);
-  EXPECT_THAT(image_upload.mime_types_allowed(), "image/*");
+  EXPECT_THAT(image_upload.mime_types_allowed(),
+              "image/avif,image/bmp,image/jpeg,image/png,image/webp,image/"
+              "heif,image/heic");
 
   auto attachment_upload = config.composebox().attachment_upload();
   EXPECT_EQ(attachment_upload.max_size_bytes(), 200000000);
@@ -230,7 +232,8 @@
   omnibox::NTPComposeboxConfig config = scoped_config_.Get().config;
   // Check that both default mime type lists were cleared.
   EXPECT_THAT(config.composebox().image_upload().mime_types_allowed(),
-              "image/*");
+              "image/avif,image/bmp,image/jpeg,image/png,image/webp,"
+              "image/heif,image/heic");
   EXPECT_THAT(config.composebox().attachment_upload().mime_types_allowed(),
               ".pdf,application/pdf");
 
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index 12651b3c..23b0dd7 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -608,7 +608,7 @@
   source->AddBoolean("composeboxShowRecentTabChip",
                      ntp_composebox::kShowRecentTabChip.Get());
   source->AddLocalizedString("askAboutThisTabAriaLabel",
-                             IDS_NTP_COMPOSE_ASK_ABOUT_THIS_TAB_ARIA_LABEL);
+                             IDS_COMPOSE_ASK_ABOUT_THIS_TAB_ARIA_LABEL);
   source->AddBoolean("composeboxShowContextMenuTabPreviews",
                      ntp_composebox::kShowContextMenuTabPreviews.Get());
   source->AddBoolean("composeboxContextMenuEnableMultiTabSelection",
diff --git a/chrome/browser/ui/webui/searchbox/searchbox_handler.cc b/chrome/browser/ui/webui/searchbox/searchbox_handler.cc
index f65b837f..d06884c 100644
--- a/chrome/browser/ui/webui/searchbox/searchbox_handler.cc
+++ b/chrome/browser/ui/webui/searchbox/searchbox_handler.cc
@@ -364,9 +364,8 @@
       {"createImages", IDS_NTP_COMPOSE_CREATE_IMAGES},
       {"composeDeepSearchPlaceholder", IDS_COMPOSE_DEEP_SEARCH_PLACEHOLDER},
       {"composeCreateImagePlaceholder", IDS_COMPOSE_CREATE_IMAGE_PLACEHOLDER},
-      {"askAboutThisTab", IDS_NTP_COMPOSE_ASK_ABOUT_THIS_TAB},
-      {"askAboutThisTabAriaLabel",
-       IDS_NTP_COMPOSE_ASK_ABOUT_THIS_TAB_ARIA_LABEL},
+      {"askAboutThisTab", IDS_COMPOSE_ASK_ABOUT_THIS_TAB},
+      {"askAboutThisTabAriaLabel", IDS_COMPOSE_ASK_ABOUT_THIS_TAB_ARIA_LABEL},
       {"removeToolChipAriaLabel", IDS_COMPOSE_REMOVE_TOOL_CHIP_A11Y_LABEL},
       {"composeFileTypesAllowedError",
        IDS_NTP_COMPOSE_FILE_TYPE_NOT_ALLOWED_ERROR},
diff --git a/chrome/browser/ui/webui/settings/sync_settings_interactive_uitest.cc b/chrome/browser/ui/webui/settings/sync_settings_interactive_uitest.cc
index cfc9ed12..619acddc 100644
--- a/chrome/browser/ui/webui/settings/sync_settings_interactive_uitest.cc
+++ b/chrome/browser/ui/webui/settings/sync_settings_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/metrics/histogram_tester.h"
 #include "base/test/test_future.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
@@ -168,6 +169,7 @@
 // signing-in from the settings menu.
 IN_PROC_BROWSER_TEST_F(SyncSettingsInteractiveTest,
                        ShowHistorySyncOptinDialogFromSettingsSignin) {
+  base::HistogramTester histogram_tester;
   // Handle the Gaia signin page.
   embedded_test_server()->StartAcceptingConnections();
 
@@ -215,7 +217,11 @@
                          UiElementHasAppeared(kHistoryOptinAcceptButton)),
       WaitForStateChange(kHistorySyncOptinDialogContentsId,
                          UiElementHasAppeared(kHistoryOptinRejectButton)));
-  // TODO(crbug.com/457428660): Add metrics checks once they are implemented.
+
+  histogram_tester.ExpectUniqueSample("Signin.HistorySyncOptIn.Started",
+                                      signin_metrics::AccessPoint::kSettings,
+                                      1);
+  histogram_tester.ExpectTotalCount("Signin.HistorySyncOptIn.Completed", 0);
 }
 
 // Tests that a signed in user on the web can trigger and see the History
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 560d016..7b70e74 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
@@ -926,7 +926,11 @@
 
 void ReadAnythingUntrustedPageHandler::OnActiveAXTreeIDChanged() {
   is_pdf_ = false;
-  if (!active_) {
+  // If the side panel is not active, we should not send the active tree id.
+  // This check is skipped when immersive read anything is enabled because
+  // there are times when the side panel is inactive but the Reading Mode
+  // application is still running, so we do need to send the active tree id.
+  if (!active_ && !features::IsImmersiveReadAnythingEnabled()) {
     VLOG(1) << "Sending unknown tree because not active";
     page_->OnActiveAXTreeIDChanged(ui::AXTreeIDUnknown(), ukm::kInvalidSourceId,
                                    /*is_pdf=*/false);
diff --git a/chrome/browser/webauthn/change_pin_controller_impl.h b/chrome/browser/webauthn/change_pin_controller_impl.h
index 1e66b7a..bcf4ed4 100644
--- a/chrome/browser/webauthn/change_pin_controller_impl.h
+++ b/chrome/browser/webauthn/change_pin_controller_impl.h
@@ -98,8 +98,6 @@
 
   static void RecordHistogram(ChangePinEvent event);
 
-  AuthenticatorRequestDialogModel* model_for_testing() { return model_.get(); }
-
  private:
   explicit ChangePinControllerImpl(content::RenderFrameHost* render_frame_host);
   friend class content::DocumentUserData<ChangePinControllerImpl>;
diff --git a/chrome/browser/webauthn/change_pin_controller_impl_browsertest.cc b/chrome/browser/webauthn/change_pin_controller_impl_browsertest.cc
deleted file mode 100644
index ededcb9b4..0000000
--- a/chrome/browser/webauthn/change_pin_controller_impl_browsertest.cc
+++ /dev/null
@@ -1,194 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/webauthn/change_pin_controller_impl.h"
-
-#include <memory>
-
-#include "base/run_loop.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/test_future.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
-#include "chrome/browser/webauthn/enclave_authenticator_browsertest_base.h"
-#include "chrome/browser/webauthn/enclave_manager.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/browser_test.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-
-class ModelObserver : public AuthenticatorRequestDialogModel::Observer {
- public:
-  explicit ModelObserver(AuthenticatorRequestDialogModel* model)
-      : model_(model) {
-    model_->observers.AddObserver(this);
-  }
-
-  ~ModelObserver() override {
-    if (model_) {
-      model_->observers.RemoveObserver(this);
-      model_ = nullptr;
-    }
-  }
-
-  void SetStepToObserve(AuthenticatorRequestDialogModel::Step step) {
-    ASSERT_FALSE(run_loop_);
-    step_ = step;
-    run_loop_ = std::make_unique<base::RunLoop>();
-  }
-
-  void WaitForStep() {
-    if (model_->step() == step_) {
-      run_loop_.reset();
-      return;
-    }
-    ASSERT_TRUE(run_loop_);
-    run_loop_->Run();
-    // When waiting for `kClosed` the model is deleted at this point.
-    if (step_ != AuthenticatorRequestDialogModel::Step::kClosed) {
-      CHECK_EQ(step_, model_->step());
-    }
-    Reset();
-  }
-
-  void OnStepTransition() override {
-    if (run_loop_ && step_ == model_->step()) {
-      run_loop_->QuitWhenIdle();
-    }
-  }
-
-  void OnModelDestroyed(AuthenticatorRequestDialogModel* model) override {
-    model_ = nullptr;
-  }
-
-  void Reset() {
-    step_ = AuthenticatorRequestDialogModel::Step::kNotStarted;
-    run_loop_.reset();
-  }
-
- private:
-  raw_ptr<AuthenticatorRequestDialogModel> model_;
-  AuthenticatorRequestDialogModel::Step step_ =
-      AuthenticatorRequestDialogModel::Step::kNotStarted;
-  std::unique_ptr<base::RunLoop> run_loop_;
-};
-
-}  // namespace
-
-class ChangePinControllerBrowserTest : public EnclaveAuthenticatorTestBase {
- public:
-  ChangePinControllerBrowserTest() = default;
-  ~ChangePinControllerBrowserTest() override = default;
-
-  void SetUpOnMainThread() override {
-    EnclaveAuthenticatorTestBase::SetUpOnMainThread();
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(
-        browser(), https_server_.GetURL("www.example.com", "/title1.html")));
-  }
-
-  ChangePinControllerImpl* GetController() {
-    content::WebContents* web_contents =
-        browser()->tab_strip_model()->GetActiveWebContents();
-    return ChangePinControllerImpl::GetOrCreateForCurrentDocument(
-        web_contents->GetPrimaryMainFrame());
-  }
-};
-
-IN_PROC_BROWSER_TEST_F(ChangePinControllerBrowserTest, ChangePin) {
-  SimulateSuccessfulGpmPinCreation("123456");
-
-  ChangePinControllerImpl* controller = GetController();
-  ASSERT_TRUE(controller);
-
-  ModelObserver observer(controller->model_for_testing());
-  base::HistogramTester histogram_tester;
-
-  base::test::TestFuture<bool> change_pin_future;
-  controller->StartChangePin(change_pin_future.GetCallback());
-
-  observer.SetStepToObserve(
-      AuthenticatorRequestDialogModel::Step::kGPMReauthForPinReset);
-  observer.WaitForStep();
-
-  controller->OnReauthComplete("rapt");
-
-  observer.SetStepToObserve(
-      AuthenticatorRequestDialogModel::Step::kGPMChangePin);
-  observer.WaitForStep();
-
-  controller->OnGPMPinEntered(u"654321");
-
-  EXPECT_TRUE(change_pin_future.Get());
-
-  histogram_tester.ExpectBucketCount(
-      "WebAuthentication.Enclave.ChangePinEvents",
-      ChangePinControllerImpl::ChangePinEvent::kFlowStartedFromSettings, 1);
-  histogram_tester.ExpectBucketCount(
-      "WebAuthentication.Enclave.ChangePinEvents",
-      ChangePinControllerImpl::ChangePinEvent::kReauthCompleted, 1);
-  histogram_tester.ExpectBucketCount(
-      "WebAuthentication.Enclave.ChangePinEvents",
-      ChangePinControllerImpl::ChangePinEvent::kNewPinEntered, 1);
-  histogram_tester.ExpectBucketCount(
-      "WebAuthentication.Enclave.ChangePinEvents",
-      ChangePinControllerImpl::ChangePinEvent::kCompletedSuccessfully, 1);
-}
-
-IN_PROC_BROWSER_TEST_F(ChangePinControllerBrowserTest,
-                       IsChangePinFlowAvailable_NoPin) {
-  ChangePinControllerImpl* controller = GetController();
-  ASSERT_TRUE(controller);
-  base::test::TestFuture<bool> future;
-  controller->IsChangePinFlowAvailable(future.GetCallback());
-  EXPECT_FALSE(future.Get());
-}
-
-IN_PROC_BROWSER_TEST_F(ChangePinControllerBrowserTest,
-                       IsChangePinFlowAvailable_HasPin) {
-  ChangePinControllerImpl* controller = GetController();
-  ASSERT_TRUE(controller);
-
-  SimulateSuccessfulGpmPinCreation("123456");
-
-  base::test::TestFuture<bool> future;
-  controller->IsChangePinFlowAvailable(future.GetCallback());
-  EXPECT_TRUE(future.Get());
-}
-
-IN_PROC_BROWSER_TEST_F(ChangePinControllerBrowserTest,
-                       ChangePin_ReauthCancelled) {
-  SimulateSuccessfulGpmPinCreation("123456");
-
-  ChangePinControllerImpl* controller = GetController();
-  ASSERT_TRUE(controller);
-
-  ModelObserver observer(controller->model_for_testing());
-  base::HistogramTester histogram_tester;
-
-  base::test::TestFuture<bool> change_pin_future;
-  controller->StartChangePin(change_pin_future.GetCallback());
-
-  observer.SetStepToObserve(
-      AuthenticatorRequestDialogModel::Step::kGPMReauthForPinReset);
-  observer.WaitForStep();
-
-  controller->OnRecoverSecurityDomainClosed();
-
-  // The flow should have failed because the reauth was cancelled.
-  EXPECT_FALSE(change_pin_future.Get());
-
-  EXPECT_EQ(controller->model_for_testing()->step(),
-            AuthenticatorRequestDialogModel::Step::kNotStarted);
-
-  histogram_tester.ExpectBucketCount(
-      "WebAuthentication.Enclave.ChangePinEvents",
-      ChangePinControllerImpl::ChangePinEvent::kFlowStartedFromSettings, 1);
-  histogram_tester.ExpectBucketCount(
-      "WebAuthentication.Enclave.ChangePinEvents",
-      ChangePinControllerImpl::ChangePinEvent::kReauthCancelled, 1);
-}
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index 8c99e968..15127e6 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1764071962-9b4eee206080ae21a674821fab8c82c4840d7d3d-cb5b465f1c35dc6070d5696d1d9fd38f6d2b6795.profdata
+chrome-android32-main-1764093274-607b8106b18f1f4e1bf3a514a01c3a067e5455d6-1b74a667c20640960e4dfab9d2d0735d4fd96017.profdata
diff --git a/chrome/build/android-desktop-x64.pgo.txt b/chrome/build/android-desktop-x64.pgo.txt
index ac724fc..0d53662b 100644
--- a/chrome/build/android-desktop-x64.pgo.txt
+++ b/chrome/build/android-desktop-x64.pgo.txt
@@ -1 +1 @@
-chrome-android-desktop-x64-main-1764071962-ae18a779bf434c4a068cbafb0476ad1f5bade84c-cb5b465f1c35dc6070d5696d1d9fd38f6d2b6795.profdata
+chrome-android-desktop-x64-main-1764093274-d93839d499529dff9e6eec33566f17c20809be09-1b74a667c20640960e4dfab9d2d0735d4fd96017.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index dea0ef5ef..55c70b2a 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1764071962-195eabce36dc684dd298782e03574fbd1042478d-cb5b465f1c35dc6070d5696d1d9fd38f6d2b6795.profdata
+chrome-linux-main-1764093274-40c2d4af2e9bed445b63bb88a7518655904b1619-1b74a667c20640960e4dfab9d2d0735d4fd96017.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 8d04b86..47ea25cd 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1764079111-f86500aea83f970da82e0d63c7023d4666a17f8e-099260ba6542ed0b2a049de07f9d5411a1907708.profdata
+chrome-mac-arm-main-1764100788-cc86b7f257c99f4e4760f0568d0e45279d517290-8aa98871058e1f8999366d0a677577fd019b3f3f.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index d6bb2f3..6d4c291 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1764071962-0a10e6ebf2769fe06bb9909c031a3d7c7a2be4c9-cb5b465f1c35dc6070d5696d1d9fd38f6d2b6795.profdata
+chrome-mac-main-1764093274-6934f271457c528e5cb88399f3585b53a6a21f98-1b74a667c20640960e4dfab9d2d0735d4fd96017.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index 6795654..745f821 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1764071962-7b369a6476fd808a044e892acf9a12503df82099-cb5b465f1c35dc6070d5696d1d9fd38f6d2b6795.profdata
+chrome-win-arm64-main-1764093274-5ce533dc3d35343ec32ea5e9bc75606e113da078-1b74a667c20640960e4dfab9d2d0735d4fd96017.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index da44778e..ed1f14bb8 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1764050017-00101c0a78ebacda542a8e251e979fc186b8dfab-4a93ac8f3a3c4f62d3369fc1e0dd177060925a92.profdata
+chrome-win32-main-1764082772-43dbc8781a33c48fd98e9c69898a7fe4db3874e0-bd0c353f3a969c10ef82b3076b1955497f84ace2.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index b7168c1..a45659e4 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1764050017-0f21f2427b2d09f07736ba3213f093dc9ac5ecd1-4a93ac8f3a3c4f62d3369fc1e0dd177060925a92.profdata
+chrome-win64-main-1764082772-b224afdf67f7b972b4d60cf6c8d5e9f020ddb84b-bd0c353f3a969c10ef82b3076b1955497f84ace2.profdata
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index e2e21a15..ec68419 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -26,6 +26,15 @@
 // letter code from ISO-639.
 const char kAcceptLang[] = "accept-lang";
 
+#if BUILDFLAG(IS_MAC)
+// Only if we're running in an unsigned build, passing this flag will allow
+// app shims whose code signature does not match what chrome is expecting to
+// still connect to chrome. This is used by some tests to allow the test to
+// pretend to be a valid app shim.
+const char kAllowAppShimSignatureMismatchForTests[] =
+    "allow-appshim-signature-mismatch-for-tests";
+#endif
+
 // Allows third-party content included on a page to prompt for a HTTP basic
 // auth username/password pair.
 const char kAllowCrossOriginAuthPrompt[] = "allow-cross-origin-auth-prompt";
@@ -246,6 +255,11 @@
 // Forces the maximum disk space to be used by the disk cache, in bytes.
 const char kDiskCacheSize[] = "disk-cache-size";
 
+#if BUILDFLAG(IS_MAC)
+// Skips initializing the shares NSApplication instance in ChromeTestSuite.
+const char kDoNotCreateNSAppForTests[] = "do-not-create-nsapp-for-tests";
+#endif
+
 // Do not de-elevate the browser on launch. Used after de-elevating to prevent
 // infinite loops.
 const char kDoNotDeElevateOnLaunch[] = "do-not-de-elevate";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 2bae0210..3acc002 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -36,6 +36,9 @@
 // All switches in alphabetical order. The switches should be documented
 // alongside the definition of their values in the .cc file.
 extern const char kAcceptLang[];
+#if BUILDFLAG(IS_MAC)
+extern const char kAllowAppShimSignatureMismatchForTests[];
+#endif
 extern const char kAllowCrossOriginAuthPrompt[];
 extern const char kAllowHttpScreenCapture[];
 extern const char kAllowRunningInsecureContent[];
@@ -87,6 +90,9 @@
 extern const char kDisableZeroBrowsersOpenForTests[];
 extern const char kDiskCacheDir[];
 extern const char kDiskCacheSize[];
+#if BUILDFLAG(IS_MAC)
+extern const char kDoNotCreateNSAppForTests[];
+#endif
 extern const char kDoNotDeElevateOnLaunch[];
 extern const char kDumpBrowserHistograms[];
 extern const char kEnableAudioDebugRecordingsFromExtension[];
diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model_browsertest.cc b/chrome/renderer/accessibility/read_anything/read_aloud_app_model_browsertest.cc
index 50a665b..fa769db 100644
--- a/chrome/renderer/accessibility/read_anything/read_aloud_app_model_browsertest.cc
+++ b/chrome/renderer/accessibility/read_anything/read_aloud_app_model_browsertest.cc
@@ -388,414 +388,6 @@
   EXPECT_EQ(DefaultLanguage(), lang2);
 }
 
-TEST_F(
-    ReadAnythingReadAloudAppModelTest,
-    GetHighlightForCurrentSegmentIndex_NodeSpansMultipleSentences_ReturnsCorrectNodes) {
-  // Text indices:            0 12345678901234 5678901234
-  std::u16string segment1 = u"I\'m taking what\'s mine! ";
-  // Text indices:            012345678901234567890123456
-  std::u16string segment2 = u"Every drop, every smidge. ";
-  // Text indices:            0123 45678901234 5678901234567890123456
-  std::u16string segment3 = u"If I\'m burning a bridge, let it burn. ";
-  // Text indices:            01234 56789012345678901
-  std::u16string segment4 = u"But I\'m crossing the ";
-
-  std::u16string node1_text = segment1 + segment2 + segment3 + segment4;
-  std::u16string node2_text = u"line.";
-
-  ui::AXNodeData static_text1 = test::TextNode(kId1, node1_text);
-  ui::AXNodeData static_text2 = test::TextNode(kId2, node2_text);
-
-  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
-      {std::move(static_text1), std::move(static_text2)});
-  // Before there are any processed granularities, GetHighlightStartIndex
-  // should return an invalid id.
-  ExpectHighlightAtIndexEmpty(1);
-
-  std::vector<ui::AXNodeID> node_ids =
-      model().GetCurrentText(false, false, &current_nodes).node_ids;
-  EXPECT_EQ(node_ids.size(), 1u);
-
-  // Storing as a separate variable so we don't need to cast every time.
-  int segment1_length = static_cast<int>(segment1.length());
-  int segment2_length = static_cast<int>(segment2.length());
-  int segment3_length = static_cast<int>(segment3.length());
-  int segment4_partial_length = static_cast<int>(segment4.length());
-  int segment4_full_length =
-      static_cast<int>(segment4.length() + node2_text.length());
-
-  // For the first node in the first segment, the returned index should equal
-  // the passed parameter.
-  ExpectHighlightAtIndexMatches(0, {{kId1, 0, 4}});
-  ExpectHighlightAtIndexMatches(6, {{kId1, 6, 11}});
-  ExpectHighlightAtIndexMatches(15, {{kId1, 15, 16}});
-  ExpectHighlightAtIndexMatches(segment1.length() - 1,
-                                {{kId1, segment1_length - 1, segment1_length}});
-  ExpectHighlightAtIndexEmpty(segment1.length());
-
-  // Move to segment 2.
-  node_ids = MoveToNextGranularityAndGetText(&current_nodes);
-  EXPECT_EQ(node_ids.size(), 1u);
-
-  // For the second segment, the boundary index will have reset for the new
-  // speech segment. The correct highlight start index is the index that the
-  // boundary index within the segment corresponds to within the node.
-  int base_length = segment1_length;
-  ExpectHighlightAtIndexMatches(0, {{kId1, base_length, base_length + 6}});
-  ExpectHighlightAtIndexMatches(10,
-                                {{kId1, base_length + 10, base_length + 12}});
-  ExpectHighlightAtIndexMatches(13,
-                                {{kId1, base_length + 13, base_length + 18}});
-
-  base_length += segment2_length;
-  ExpectHighlightAtIndexMatches(segment2_length - 1,
-                                {{kId1, base_length - 1, base_length}});
-  ExpectHighlightAtIndexEmpty(segment1_length + segment2_length);
-
-  // Move to segment 3.
-  node_ids = MoveToNextGranularityAndGetText(&current_nodes);
-  EXPECT_EQ(node_ids.size(), 1u);
-
-  // For the third segment, the boundary index will have reset for the new
-  // speech segment. The correct highlight start index is the index that the
-  // boundary index within the segment corresponds to within the node.
-  ExpectHighlightAtIndexMatches(0, {{kId1, base_length, base_length + 3}});
-  ExpectHighlightAtIndexMatches(9, {{kId1, base_length + 9, base_length + 15}});
-  ExpectHighlightAtIndexMatches(13,
-                                {{kId1, base_length + 13, base_length + 15}});
-  ExpectHighlightAtIndexMatches(segment3_length - 1,
-                                {{kId1, base_length + segment3_length - 1,
-                                  base_length + segment3_length}});
-
-  ExpectHighlightAtIndexEmpty(base_length + segment3.length());
-
-  // Move to segment 4.
-  node_ids = MoveToNextGranularityAndGetText(&current_nodes);
-  EXPECT_EQ(node_ids.size(), 2u);
-  EXPECT_EQ(node_ids[0], kId1);
-  EXPECT_EQ(node_ids[1], kId2);
-
-  // For the fourth segment, there are two nodes. For the first node,
-  // the correct highlight start corresponds to the index within the first
-  // node.
-  base_length += segment3_length;
-  ExpectHighlightAtIndexMatches(0, {{kId1, base_length, base_length + 4}});
-  ExpectHighlightAtIndexMatches(2, {{kId1, base_length + 2, base_length + 4}});
-  ExpectHighlightAtIndexMatches(8, {{kId1, base_length + 8, base_length + 17}});
-  ExpectHighlightAtIndexMatches(
-      segment4_partial_length - 1,
-      {{kId1, base_length + segment4_partial_length - 1,
-        base_length + segment4_partial_length}});
-
-  ExpectHighlightAtIndexEmpty(
-      static_cast<int>(base_length + segment4.length()));
-
-  // For the second node, the highlight index corresponds to the position
-  // within the second node.
-  ExpectHighlightAtIndexMatches(segment4_partial_length, {{kId2, 0, 5}});
-  ExpectHighlightAtIndexMatches(segment4_partial_length + 2, {{kId2, 2, 5}});
-  ExpectHighlightAtIndexMatches(
-      segment4_full_length - 1,
-      {{kId2, static_cast<int>(node2_text.length() - 1),
-        static_cast<int>(node2_text.length())}});
-
-  ExpectHighlightAtIndexEmpty(
-      static_cast<int>(segment4.length() + node2_text.length()));
-}
-
-TEST_F(ReadAnythingReadAloudAppModelTest,
-       GetHighlightForCurrentSegmentIndex_AfterNext_ReturnsCorrectNodes) {
-  // Text indices:             012345678901234567890123456789012
-  std::u16string sentence1 = u"Never feel heavy or earthbound. ";
-  std::u16string sentence2 = u"No worries or doubts ";
-  std::u16string sentence3 = u"interfere.";
-
-  ui::AXNodeData static_text1 = test::TextNode(kId1, sentence1);
-  ui::AXNodeData static_text2 = test::TextNode(kId2, sentence2);
-  ui::AXNodeData static_text3 = test::TextNode(kId3, sentence3);
-
-  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
-      {std::move(static_text1), std::move(static_text2),
-       std::move(static_text3)});
-
-  // Before there are any processed granularities,
-  // GetNodeIdForCurrentSegmentIndex should return an invalid id.
-  ExpectHighlightAtIndexEmpty(1);
-
-  std::vector<ui::AXNodeID> node_ids =
-      model().GetCurrentText(false, false, &current_nodes).node_ids;
-  EXPECT_EQ(node_ids.size(), 1u);
-
-  // Spot check that indices 0->sentence1.length() map to the first node id.
-  ExpectHighlightAtIndexMatches(0, {{kId1, 0, 6}});
-  ExpectHighlightAtIndexMatches(7, {{kId1, 7, 11}});
-  ExpectHighlightAtIndexMatches(sentence1.length() - 1, {{kId1, 31, 32}});
-  ExpectHighlightAtIndexEmpty(sentence1.length());
-
-  // Move to the next granularity.
-  node_ids = MoveToNextGranularityAndGetText(&current_nodes);
-  EXPECT_EQ(node_ids.size(), 2u);
-
-  // Spot check that indices in sentence 2 map to the second node id.
-  ExpectHighlightAtIndexMatches(0, {{kId2, 0, 3}});
-  ExpectHighlightAtIndexMatches(7, {{kId2, 7, 11}});
-  ExpectHighlightAtIndexMatches(sentence2.length() - 1, {{kId2, 20, 21}});
-
-  // Spot check that indices in sentence 3 map to the third node id.
-  ExpectHighlightAtIndexMatches(sentence2.length() + 1, {{kId3, 1, 10}});
-  ExpectHighlightAtIndexMatches(27, {{kId3, 6, 10}});
-  ExpectHighlightAtIndexMatches(sentence2.length() + sentence3.length() - 1,
-                                {{kId3, 9, 10}});
-
-  // Out-of-bounds nodes return invalid.
-  ExpectHighlightAtIndexEmpty(sentence2.length() + sentence3.length() + 1);
-}
-
-TEST_F(ReadAnythingReadAloudAppModelTest,
-       GetHighlightForCurrentSegmentIndex_AfterPrevious_ReturnsCorrectNodes) {
-  // Text indices:             01234567890123456789012345678901234567890
-  std::u16string sentence1 = u"There's nothing but you ";
-  std::u16string sentence2 = u"looking down on the view from up here. ";
-  std::u16string sentence3 = u"Stretch out with the wind behind you.";
-
-  ui::AXNodeData static_text1 = test::TextNode(kId1, sentence1);
-  ui::AXNodeData static_text2 = test::TextNode(kId2, sentence2);
-  ui::AXNodeData static_text3 = test::TextNode(kId3, sentence3);
-
-  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
-      {std::move(static_text1), std::move(static_text2),
-       std::move(static_text3)});
-
-  // Before there are any processed granularities,
-  // GetNodeIdForCurrentSegmentIndex should return an invalid id.
-  ExpectHighlightAtIndexEmpty(1);
-
-  std::vector<ui::AXNodeID> node_ids =
-      model().GetCurrentText(false, false, &current_nodes).node_ids;
-  EXPECT_EQ(node_ids.size(), 2u);
-
-  // Move forward.
-  node_ids = MoveToNextGranularityAndGetText(&current_nodes);
-  EXPECT_EQ(node_ids.size(), 1u);
-
-  // Spot check that indices 0->sentence3.length() map to the third node id.
-  ExpectHighlightAtIndexMatches(0, {{kId3, 0, 8}});
-  ExpectHighlightAtIndexMatches(7, {{kId3, 7, 8}});
-  ExpectHighlightAtIndexMatches(sentence3.length() - 1, {{kId3, 36, 37}});
-
-  // Move backwards.
-  node_ids = MoveToPreviousGranularityAndGetText(&current_nodes);
-  EXPECT_EQ(node_ids.size(), 2u);
-
-  // Spot check that indices in sentence 1 map to the first node id.
-  ExpectHighlightAtIndexMatches(0, {{kId1, 0, 8}});
-  ExpectHighlightAtIndexMatches(6, {{kId1, 6, 8}});
-  ExpectHighlightAtIndexMatches(sentence1.length() - 1, {{kId1, 23, 24}});
-
-  // Spot check that indices in sentence 2 map to the second node id.
-  ExpectHighlightAtIndexMatches(sentence1.length() + 1, {{kId2, 1, 8}});
-  ExpectHighlightAtIndexMatches(27, {{kId2, 3, 8}});
-  ExpectHighlightAtIndexMatches(sentence1.length() + sentence2.length() - 1,
-                                {{kId2, 38, 39}});
-
-  // Out-of-bounds nodes return invalid.
-  ExpectHighlightAtIndexEmpty(sentence1.length() + sentence2.length() + 1);
-}
-
-TEST_F(ReadAnythingReadAloudAppModelTest,
-       GetHighlightForCurrentSegmentIndex_MultinodeWords_ReturnsCorrectLength) {
-  std::u16string word1 = u"Stretch ";
-  std::u16string word2 = u"out ";
-  std::u16string word3 = u"with ";
-  std::u16string word4 = u"the ";
-  std::u16string word5 = u"wind ";
-  std::u16string word6 = u"beh";
-  std::u16string word7 = u"ind ";
-  std::u16string word8 = u"you.";
-  std::u16string sentence1 = word1 + word2 + word3 + word4 + word5 + word6;
-  std::u16string sentence2 = word7 + word8;
-
-  ui::AXNodeData static_text1 = test::TextNode(kId1, sentence1);
-  ui::AXNodeData static_text2 = test::TextNode(kId2, sentence2);
-
-  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
-      {std::move(static_text1), std::move(static_text2)});
-
-  // Before there are any processed granularities,
-  // GetNodeIdForCurrentSegmentIndex should return an invalid id.
-  ExpectHighlightAtIndexEmpty(1);
-
-  std::vector<ui::AXNodeID> node_ids =
-      model().GetCurrentText(false, false, &current_nodes).node_ids;
-  EXPECT_EQ(node_ids.size(), 2u);
-
-  // Throughout first word.
-  ExpectHighlightAtIndexMatches(0, {{kId1, 0, 8}});
-  ExpectHighlightAtIndexMatches(2, {{kId1, 2, 8}});
-  ExpectHighlightAtIndexMatches(word1.length() - 2, {{kId1, 6, 8}});
-
-  // Throughout third word.
-  int third_word_index = sentence1.find(word3);
-  ExpectHighlightAtIndexMatches(third_word_index, {{kId1, 12, 17}});
-  ExpectHighlightAtIndexMatches(third_word_index + 2, {{kId1, 14, 17}});
-
-  // Words split across node boundaries
-  int sixth_word_index = sentence1.find(word6);
-  ExpectHighlightAtIndexMatches(sixth_word_index,
-                                {{kId1, 26, 29}, {kId2, 0, 4}});
-  ExpectHighlightAtIndexMatches(sixth_word_index + 2,
-                                {{kId1, 28, 29}, {kId2, 0, 4}});
-
-  int seventh_word_index = sentence1.length();
-  ExpectHighlightAtIndexMatches(seventh_word_index, {{kId2, 0, 4}});
-  ExpectHighlightAtIndexMatches(seventh_word_index + 2, {{kId2, 2, 4}});
-
-  int last_word_index = sentence1.length() + sentence2.find(word8);
-  ExpectHighlightAtIndexMatches(last_word_index, {{kId2, 4, 8}});
-  ExpectHighlightAtIndexMatches(last_word_index + 2, {{kId2, 6, 8}});
-
-  // Boundary testing.
-  ExpectHighlightAtIndexEmpty(-5);
-  ExpectHighlightAtIndexEmpty(sentence1.length() + sentence2.length());
-  ExpectHighlightAtIndexEmpty(sentence1.length() + sentence2.length() + 1);
-}
-
-TEST_F(ReadAnythingReadAloudAppModelTest,
-       GetCurrentTextSegments_ReturnsCorrectSegments) {
-  std::u16string sentence = u"I broke into a million pieces";
-  ui::AXNodeData static_text = test::TextNode(kId1, sentence);
-  const std::set<ui::AXNodeID> current_nodes =
-      InitializeWithAndProcessNodes({std::move(static_text)});
-
-  std::vector<ReadAloudTextSegment> segments =
-      model().GetCurrentTextSegments(false, false, &current_nodes);
-
-  EXPECT_EQ(1u, segments.size());
-  EXPECT_EQ(kId1, segments.at(0).id);
-  EXPECT_EQ(0u, segments.at(0).text_start);
-  EXPECT_EQ(sentence.size(), segments.at(0).text_end);
-}
-
-TEST_F(
-    ReadAnythingReadAloudAppModelTest,
-    GetCurrentTextSegments_SentenceSpansMultipleNodes_ReturnsCorrectSegments) {
-  std::u16string sentence1 = u"and I can't go back, ";
-  std::u16string sentence2 = u"But now I'm seeing all the beauty ";
-  std::u16string sentence3 = u"in the broken glass.";
-  ui::AXNodeData static_text1 = test::TextNode(kId1, sentence1);
-  ui::AXNodeData static_text2 = test::TextNode(kId2, sentence2);
-  ui::AXNodeData static_text3 = test::TextNode(kId3, sentence3);
-  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
-      {std::move(static_text1), std::move(static_text2),
-       std::move(static_text3)});
-
-  std::vector<ReadAloudTextSegment> segments =
-      model().GetCurrentTextSegments(false, false, &current_nodes);
-
-  EXPECT_EQ(3u, segments.size());
-  EXPECT_EQ(kId1, segments.at(0).id);
-  EXPECT_EQ(0u, segments.at(0).text_start);
-  EXPECT_EQ(sentence1.size(), segments.at(0).text_end);
-  EXPECT_EQ(kId2, segments.at(1).id);
-  EXPECT_EQ(0u, segments.at(1).text_start);
-  EXPECT_EQ(sentence2.size(), segments.at(1).text_end);
-  EXPECT_EQ(kId3, segments.at(2).id);
-  EXPECT_EQ(0u, segments.at(2).text_start);
-  EXPECT_EQ(sentence3.size(), segments.at(2).text_end);
-}
-
-TEST_F(
-    ReadAnythingReadAloudAppModelTest,
-    GetCurrentTextSegments_NodeSpansMultipleSentences_ReturnsCorrectSegments) {
-  std::u16string segment1 = u"The scars are part of me! ";
-  std::u16string segment2 = u"Darkness and harmony. ";
-  std::u16string segment3 = u"My voice without the lies. ";
-  std::u16string segment4 = u"This is what it sounds ";
-  std::u16string node1_text = segment1 + segment2 + segment3 + segment4;
-  std::u16string node2_text = u"like.";
-  int end1 = segment1.size();
-  int end2 = end1 + segment2.size();
-  int end3 = end2 + segment3.size();
-  int end4 = end3 + segment4.size();
-  ui::AXNodeData static_text1 = test::TextNode(kId1, node1_text);
-  ui::AXNodeData static_text2 = test::TextNode(kId2, node2_text);
-  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
-      {std::move(static_text1), std::move(static_text2)});
-
-  // The first sentence is all of segment1.
-  std::vector<ReadAloudTextSegment> segments =
-      model().GetCurrentTextSegments(false, false, &current_nodes);
-  EXPECT_EQ(1u, segments.size());
-  EXPECT_EQ(kId1, segments.at(0).id);
-  EXPECT_EQ(0u, segments.at(0).text_start);
-  EXPECT_EQ(end1, segments.at(0).text_end);
-
-  // Next sentence is all of segment2.
-  MoveToNextGranularityAndGetText(&current_nodes);
-  segments = model().GetCurrentTextSegments(false, false, &current_nodes);
-  EXPECT_EQ(1u, segments.size());
-  EXPECT_EQ(kId1, segments.at(0).id);
-  EXPECT_EQ(end1, segments.at(0).text_start);
-  EXPECT_EQ(end2, segments.at(0).text_end);
-
-  // Next sentence is all of segment3.
-  MoveToNextGranularityAndGetText(&current_nodes);
-  segments = model().GetCurrentTextSegments(false, false, &current_nodes);
-  EXPECT_EQ(1u, segments.size());
-  EXPECT_EQ(kId1, segments.at(0).id);
-  EXPECT_EQ(end2, segments.at(0).text_start);
-  EXPECT_EQ(end3, segments.at(0).text_end);
-
-  // Final sentence is all of segment4 in node1 plus all of node2.
-  MoveToNextGranularityAndGetText(&current_nodes);
-  segments = model().GetCurrentTextSegments(false, false, &current_nodes);
-  EXPECT_EQ(2u, segments.size());
-  EXPECT_EQ(kId1, segments.at(0).id);
-  EXPECT_EQ(end3, segments.at(0).text_start);
-  EXPECT_EQ(end4, segments.at(0).text_end);
-  EXPECT_EQ(kId2, segments.at(1).id);
-  EXPECT_EQ(0, segments.at(1).text_start);
-  EXPECT_EQ(node2_text.size(), segments.at(1).text_end);
-}
-
-TEST_F(ReadAnythingReadAloudAppModelTest,
-       GetCurrentTextSegments_AfterPrevious_ReturnsCorrectNodes) {
-  std::u16string sentence1 =
-      u"Why did I cover up the colors stuck inside my head? ";
-  std::u16string sentence2 = u"I should've let the jagged edges ";
-  std::u16string sentence3 = u"meet the light instead.";
-  ui::AXNodeData static_text1 = test::TextNode(kId1, sentence1);
-  ui::AXNodeData static_text2 = test::TextNode(kId2, sentence2);
-  ui::AXNodeData static_text3 = test::TextNode(kId3, sentence3);
-  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
-      {std::move(static_text1), std::move(static_text2),
-       std::move(static_text3)});
-
-  std::vector<ReadAloudTextSegment> segments =
-      model().GetCurrentTextSegments(false, false, &current_nodes);
-  EXPECT_EQ(1u, segments.size());
-  EXPECT_EQ(kId1, segments.at(0).id);
-  EXPECT_EQ(0u, segments.at(0).text_start);
-  EXPECT_EQ(sentence1.size(), segments.at(0).text_end);
-
-  MoveToNextGranularityAndGetText(&current_nodes);
-  segments = model().GetCurrentTextSegments(false, false, &current_nodes);
-  EXPECT_EQ(2u, segments.size());
-  EXPECT_EQ(kId2, segments.at(0).id);
-  EXPECT_EQ(0u, segments.at(0).text_start);
-  EXPECT_EQ(sentence2.size(), segments.at(0).text_end);
-  EXPECT_EQ(kId3, segments.at(1).id);
-  EXPECT_EQ(0u, segments.at(1).text_start);
-  EXPECT_EQ(sentence3.size(), segments.at(1).text_end);
-
-  MoveToPreviousGranularityAndGetText(&current_nodes);
-  segments = model().GetCurrentTextSegments(false, false, &current_nodes);
-  EXPECT_EQ(1u, segments.size());
-  EXPECT_EQ(kId1, segments.at(0).id);
-  EXPECT_EQ(0u, segments.at(0).text_start);
-  EXPECT_EQ(sentence1.size(), segments.at(0).text_end);
-}
-
 // TODO: crbug.com/440400392 - Ensure that phrase highlighting works with the
 // TS text segmentation implementation.
 class ReadAnythingReadAloudAppModelV8SegmentationTest
@@ -1199,3 +791,411 @@
   EXPECT_TRUE(base::Contains(after_reset.node_ids, kId1));
   EXPECT_EQ(first_granularity.text, sentence1);
 }
+
+TEST_F(ReadAnythingReadAloudAppModelV8SegmentationTest,
+       GetCurrentTextSegments_ReturnsCorrectSegments) {
+  std::u16string sentence = u"I broke into a million pieces";
+  ui::AXNodeData static_text = test::TextNode(kId1, sentence);
+  const std::set<ui::AXNodeID> current_nodes =
+      InitializeWithAndProcessNodes({std::move(static_text)});
+
+  std::vector<ReadAloudTextSegment> segments =
+      model().GetCurrentTextSegments(false, false, &current_nodes);
+
+  EXPECT_EQ(1u, segments.size());
+  EXPECT_EQ(kId1, segments.at(0).id);
+  EXPECT_EQ(0u, segments.at(0).text_start);
+  EXPECT_EQ(sentence.size(), segments.at(0).text_end);
+}
+
+TEST_F(
+    ReadAnythingReadAloudAppModelV8SegmentationTest,
+    GetCurrentTextSegments_SentenceSpansMultipleNodes_ReturnsCorrectSegments) {
+  std::u16string sentence1 = u"and I can't go back, ";
+  std::u16string sentence2 = u"But now I'm seeing all the beauty ";
+  std::u16string sentence3 = u"in the broken glass.";
+  ui::AXNodeData static_text1 = test::TextNode(kId1, sentence1);
+  ui::AXNodeData static_text2 = test::TextNode(kId2, sentence2);
+  ui::AXNodeData static_text3 = test::TextNode(kId3, sentence3);
+  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
+      {std::move(static_text1), std::move(static_text2),
+       std::move(static_text3)});
+
+  std::vector<ReadAloudTextSegment> segments =
+      model().GetCurrentTextSegments(false, false, &current_nodes);
+
+  EXPECT_EQ(3u, segments.size());
+  EXPECT_EQ(kId1, segments.at(0).id);
+  EXPECT_EQ(0u, segments.at(0).text_start);
+  EXPECT_EQ(sentence1.size(), segments.at(0).text_end);
+  EXPECT_EQ(kId2, segments.at(1).id);
+  EXPECT_EQ(0u, segments.at(1).text_start);
+  EXPECT_EQ(sentence2.size(), segments.at(1).text_end);
+  EXPECT_EQ(kId3, segments.at(2).id);
+  EXPECT_EQ(0u, segments.at(2).text_start);
+  EXPECT_EQ(sentence3.size(), segments.at(2).text_end);
+}
+
+TEST_F(
+    ReadAnythingReadAloudAppModelV8SegmentationTest,
+    GetHighlightForCurrentSegmentIndex_NodeSpansMultipleSentences_ReturnsCorrectNodes) {
+  // Text indices:            0 12345678901234 5678901234
+  std::u16string segment1 = u"I\'m taking what\'s mine! ";
+  // Text indices:            012345678901234567890123456
+  std::u16string segment2 = u"Every drop, every smidge. ";
+  // Text indices:            0123 45678901234 5678901234567890123456
+  std::u16string segment3 = u"If I\'m burning a bridge, let it burn. ";
+  // Text indices:            01234 56789012345678901
+  std::u16string segment4 = u"But I\'m crossing the ";
+
+  std::u16string node1_text = segment1 + segment2 + segment3 + segment4;
+  std::u16string node2_text = u"line.";
+
+  ui::AXNodeData static_text1 = test::TextNode(kId1, node1_text);
+  ui::AXNodeData static_text2 = test::TextNode(kId2, node2_text);
+
+  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
+      {std::move(static_text1), std::move(static_text2)});
+  // Before there are any processed granularities, GetHighlightStartIndex
+  // should return an invalid id.
+  ExpectHighlightAtIndexEmpty(1);
+
+  std::vector<ui::AXNodeID> node_ids =
+      model().GetCurrentText(false, false, &current_nodes).node_ids;
+  EXPECT_EQ(node_ids.size(), 1u);
+
+  // Storing as a separate variable so we don't need to cast every time.
+  int segment1_length = static_cast<int>(segment1.length());
+  int segment2_length = static_cast<int>(segment2.length());
+  int segment3_length = static_cast<int>(segment3.length());
+  int segment4_partial_length = static_cast<int>(segment4.length());
+  int segment4_full_length =
+      static_cast<int>(segment4.length() + node2_text.length());
+
+  // For the first node in the first segment, the returned index should equal
+  // the passed parameter.
+  ExpectHighlightAtIndexMatches(0, {{kId1, 0, 4}});
+  ExpectHighlightAtIndexMatches(6, {{kId1, 6, 11}});
+  ExpectHighlightAtIndexMatches(15, {{kId1, 15, 16}});
+  ExpectHighlightAtIndexMatches(segment1.length() - 1,
+                                {{kId1, segment1_length - 1, segment1_length}});
+  ExpectHighlightAtIndexEmpty(segment1.length());
+
+  // Move to segment 2.
+  node_ids = MoveToNextGranularityAndGetText(&current_nodes);
+  EXPECT_EQ(node_ids.size(), 1u);
+
+  // For the second segment, the boundary index will have reset for the new
+  // speech segment. The correct highlight start index is the index that the
+  // boundary index within the segment corresponds to within the node.
+  int base_length = segment1_length;
+  ExpectHighlightAtIndexMatches(0, {{kId1, base_length, base_length + 6}});
+  ExpectHighlightAtIndexMatches(10,
+                                {{kId1, base_length + 10, base_length + 12}});
+  ExpectHighlightAtIndexMatches(13,
+                                {{kId1, base_length + 13, base_length + 18}});
+
+  base_length += segment2_length;
+  ExpectHighlightAtIndexMatches(segment2_length - 1,
+                                {{kId1, base_length - 1, base_length}});
+  ExpectHighlightAtIndexEmpty(segment1_length + segment2_length);
+
+  // Move to segment 3.
+  node_ids = MoveToNextGranularityAndGetText(&current_nodes);
+  EXPECT_EQ(node_ids.size(), 1u);
+
+  // For the third segment, the boundary index will have reset for the new
+  // speech segment. The correct highlight start index is the index that the
+  // boundary index within the segment corresponds to within the node.
+  ExpectHighlightAtIndexMatches(0, {{kId1, base_length, base_length + 3}});
+  ExpectHighlightAtIndexMatches(9, {{kId1, base_length + 9, base_length + 15}});
+  ExpectHighlightAtIndexMatches(13,
+                                {{kId1, base_length + 13, base_length + 15}});
+  ExpectHighlightAtIndexMatches(segment3_length - 1,
+                                {{kId1, base_length + segment3_length - 1,
+                                  base_length + segment3_length}});
+
+  ExpectHighlightAtIndexEmpty(base_length + segment3.length());
+
+  // Move to segment 4.
+  node_ids = MoveToNextGranularityAndGetText(&current_nodes);
+  EXPECT_EQ(node_ids.size(), 2u);
+  EXPECT_EQ(node_ids[0], kId1);
+  EXPECT_EQ(node_ids[1], kId2);
+
+  // For the fourth segment, there are two nodes. For the first node,
+  // the correct highlight start corresponds to the index within the first
+  // node.
+  base_length += segment3_length;
+  ExpectHighlightAtIndexMatches(0, {{kId1, base_length, base_length + 4}});
+  ExpectHighlightAtIndexMatches(2, {{kId1, base_length + 2, base_length + 4}});
+  ExpectHighlightAtIndexMatches(8, {{kId1, base_length + 8, base_length + 17}});
+  ExpectHighlightAtIndexMatches(
+      segment4_partial_length - 1,
+      {{kId1, base_length + segment4_partial_length - 1,
+        base_length + segment4_partial_length}});
+
+  ExpectHighlightAtIndexEmpty(
+      static_cast<int>(base_length + segment4.length()));
+
+  // For the second node, the highlight index corresponds to the position
+  // within the second node.
+  ExpectHighlightAtIndexMatches(segment4_partial_length, {{kId2, 0, 5}});
+  ExpectHighlightAtIndexMatches(segment4_partial_length + 2, {{kId2, 2, 5}});
+  ExpectHighlightAtIndexMatches(
+      segment4_full_length - 1,
+      {{kId2, static_cast<int>(node2_text.length() - 1),
+        static_cast<int>(node2_text.length())}});
+
+  ExpectHighlightAtIndexEmpty(
+      static_cast<int>(segment4.length() + node2_text.length()));
+}
+
+TEST_F(ReadAnythingReadAloudAppModelV8SegmentationTest,
+       GetHighlightForCurrentSegmentIndex_AfterPrevious_ReturnsCorrectNodes) {
+  // Text indices:             01234567890123456789012345678901234567890
+  std::u16string sentence1 = u"There's nothing but you ";
+  std::u16string sentence2 = u"looking down on the view from up here. ";
+  std::u16string sentence3 = u"Stretch out with the wind behind you.";
+
+  ui::AXNodeData static_text1 = test::TextNode(kId1, sentence1);
+  ui::AXNodeData static_text2 = test::TextNode(kId2, sentence2);
+  ui::AXNodeData static_text3 = test::TextNode(kId3, sentence3);
+
+  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
+      {std::move(static_text1), std::move(static_text2),
+       std::move(static_text3)});
+
+  // Before there are any processed granularities,
+  // GetNodeIdForCurrentSegmentIndex should return an invalid id.
+  ExpectHighlightAtIndexEmpty(1);
+
+  std::vector<ui::AXNodeID> node_ids =
+      model().GetCurrentText(false, false, &current_nodes).node_ids;
+  EXPECT_EQ(node_ids.size(), 2u);
+
+  // Move forward.
+  node_ids = MoveToNextGranularityAndGetText(&current_nodes);
+  EXPECT_EQ(node_ids.size(), 1u);
+
+  // Spot check that indices 0->sentence3.length() map to the third node id.
+  ExpectHighlightAtIndexMatches(0, {{kId3, 0, 8}});
+  ExpectHighlightAtIndexMatches(7, {{kId3, 7, 8}});
+  ExpectHighlightAtIndexMatches(sentence3.length() - 1, {{kId3, 36, 37}});
+
+  // Move backwards.
+  node_ids = MoveToPreviousGranularityAndGetText(&current_nodes);
+  EXPECT_EQ(node_ids.size(), 2u);
+
+  // Spot check that indices in sentence 1 map to the first node id.
+  ExpectHighlightAtIndexMatches(0, {{kId1, 0, 8}});
+  ExpectHighlightAtIndexMatches(6, {{kId1, 6, 8}});
+  ExpectHighlightAtIndexMatches(sentence1.length() - 1, {{kId1, 23, 24}});
+
+  // Spot check that indices in sentence 2 map to the second node id.
+  ExpectHighlightAtIndexMatches(sentence1.length() + 1, {{kId2, 1, 8}});
+  ExpectHighlightAtIndexMatches(27, {{kId2, 3, 8}});
+  ExpectHighlightAtIndexMatches(sentence1.length() + sentence2.length() - 1,
+                                {{kId2, 38, 39}});
+
+  // Out-of-bounds nodes return invalid.
+  ExpectHighlightAtIndexEmpty(sentence1.length() + sentence2.length() + 1);
+}
+
+TEST_F(ReadAnythingReadAloudAppModelV8SegmentationTest,
+       GetHighlightForCurrentSegmentIndex_AfterNext_ReturnsCorrectNodes) {
+  // Text indices:             012345678901234567890123456789012
+  std::u16string sentence1 = u"Never feel heavy or earthbound. ";
+  std::u16string sentence2 = u"No worries or doubts ";
+  std::u16string sentence3 = u"interfere.";
+
+  ui::AXNodeData static_text1 = test::TextNode(kId1, sentence1);
+  ui::AXNodeData static_text2 = test::TextNode(kId2, sentence2);
+  ui::AXNodeData static_text3 = test::TextNode(kId3, sentence3);
+
+  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
+      {std::move(static_text1), std::move(static_text2),
+       std::move(static_text3)});
+
+  // Before there are any processed granularities,
+  // GetNodeIdForCurrentSegmentIndex should return an invalid id.
+  ExpectHighlightAtIndexEmpty(1);
+
+  std::vector<ui::AXNodeID> node_ids =
+      model().GetCurrentText(false, false, &current_nodes).node_ids;
+  EXPECT_EQ(node_ids.size(), 1u);
+
+  // Spot check that indices 0->sentence1.length() map to the first node id.
+  ExpectHighlightAtIndexMatches(0, {{kId1, 0, 6}});
+  ExpectHighlightAtIndexMatches(7, {{kId1, 7, 11}});
+  ExpectHighlightAtIndexMatches(sentence1.length() - 1, {{kId1, 31, 32}});
+  ExpectHighlightAtIndexEmpty(sentence1.length());
+
+  // Move to the next granularity.
+  node_ids = MoveToNextGranularityAndGetText(&current_nodes);
+  EXPECT_EQ(node_ids.size(), 2u);
+
+  // Spot check that indices in sentence 2 map to the second node id.
+  ExpectHighlightAtIndexMatches(0, {{kId2, 0, 3}});
+  ExpectHighlightAtIndexMatches(7, {{kId2, 7, 11}});
+  ExpectHighlightAtIndexMatches(sentence2.length() - 1, {{kId2, 20, 21}});
+
+  // Spot check that indices in sentence 3 map to the third node id.
+  ExpectHighlightAtIndexMatches(sentence2.length() + 1, {{kId3, 1, 10}});
+  ExpectHighlightAtIndexMatches(27, {{kId3, 6, 10}});
+  ExpectHighlightAtIndexMatches(sentence2.length() + sentence3.length() - 1,
+                                {{kId3, 9, 10}});
+
+  // Out-of-bounds nodes return invalid.
+  ExpectHighlightAtIndexEmpty(sentence2.length() + sentence3.length() + 1);
+}
+
+TEST_F(ReadAnythingReadAloudAppModelV8SegmentationTest,
+       GetCurrentTextSegments_AfterPrevious_ReturnsCorrectNodes) {
+  std::u16string sentence1 =
+      u"Why did I cover up the colors stuck inside my head? ";
+  std::u16string sentence2 = u"I should've let the jagged edges ";
+  std::u16string sentence3 = u"meet the light instead.";
+  ui::AXNodeData static_text1 = test::TextNode(kId1, sentence1);
+  ui::AXNodeData static_text2 = test::TextNode(kId2, sentence2);
+  ui::AXNodeData static_text3 = test::TextNode(kId3, sentence3);
+  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
+      {std::move(static_text1), std::move(static_text2),
+       std::move(static_text3)});
+
+  std::vector<ReadAloudTextSegment> segments =
+      model().GetCurrentTextSegments(false, false, &current_nodes);
+  EXPECT_EQ(1u, segments.size());
+  EXPECT_EQ(kId1, segments.at(0).id);
+  EXPECT_EQ(0u, segments.at(0).text_start);
+  EXPECT_EQ(sentence1.size(), segments.at(0).text_end);
+
+  MoveToNextGranularityAndGetText(&current_nodes);
+  segments = model().GetCurrentTextSegments(false, false, &current_nodes);
+  EXPECT_EQ(2u, segments.size());
+  EXPECT_EQ(kId2, segments.at(0).id);
+  EXPECT_EQ(0u, segments.at(0).text_start);
+  EXPECT_EQ(sentence2.size(), segments.at(0).text_end);
+  EXPECT_EQ(kId3, segments.at(1).id);
+  EXPECT_EQ(0u, segments.at(1).text_start);
+  EXPECT_EQ(sentence3.size(), segments.at(1).text_end);
+
+  MoveToPreviousGranularityAndGetText(&current_nodes);
+  segments = model().GetCurrentTextSegments(false, false, &current_nodes);
+  EXPECT_EQ(1u, segments.size());
+  EXPECT_EQ(kId1, segments.at(0).id);
+  EXPECT_EQ(0u, segments.at(0).text_start);
+  EXPECT_EQ(sentence1.size(), segments.at(0).text_end);
+}
+
+TEST_F(ReadAnythingReadAloudAppModelV8SegmentationTest,
+       GetHighlightForCurrentSegmentIndex_MultinodeWords_ReturnsCorrectLength) {
+  std::u16string word1 = u"Stretch ";
+  std::u16string word2 = u"out ";
+  std::u16string word3 = u"with ";
+  std::u16string word4 = u"the ";
+  std::u16string word5 = u"wind ";
+  std::u16string word6 = u"beh";
+  std::u16string word7 = u"ind ";
+  std::u16string word8 = u"you.";
+  std::u16string sentence1 = word1 + word2 + word3 + word4 + word5 + word6;
+  std::u16string sentence2 = word7 + word8;
+
+  ui::AXNodeData static_text1 = test::TextNode(kId1, sentence1);
+  ui::AXNodeData static_text2 = test::TextNode(kId2, sentence2);
+
+  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
+      {std::move(static_text1), std::move(static_text2)});
+
+  // Before there are any processed granularities,
+  // GetNodeIdForCurrentSegmentIndex should return an invalid id.
+  ExpectHighlightAtIndexEmpty(1);
+
+  std::vector<ui::AXNodeID> node_ids =
+      model().GetCurrentText(false, false, &current_nodes).node_ids;
+  EXPECT_EQ(node_ids.size(), 2u);
+
+  // Throughout first word.
+  ExpectHighlightAtIndexMatches(0, {{kId1, 0, 8}});
+  ExpectHighlightAtIndexMatches(2, {{kId1, 2, 8}});
+  ExpectHighlightAtIndexMatches(word1.length() - 2, {{kId1, 6, 8}});
+
+  // Throughout third word.
+  int third_word_index = sentence1.find(word3);
+  ExpectHighlightAtIndexMatches(third_word_index, {{kId1, 12, 17}});
+  ExpectHighlightAtIndexMatches(third_word_index + 2, {{kId1, 14, 17}});
+
+  // Words split across node boundaries
+  int sixth_word_index = sentence1.find(word6);
+  ExpectHighlightAtIndexMatches(sixth_word_index,
+                                {{kId1, 26, 29}, {kId2, 0, 4}});
+  ExpectHighlightAtIndexMatches(sixth_word_index + 2,
+                                {{kId1, 28, 29}, {kId2, 0, 4}});
+
+  int seventh_word_index = sentence1.length();
+  ExpectHighlightAtIndexMatches(seventh_word_index, {{kId2, 0, 4}});
+  ExpectHighlightAtIndexMatches(seventh_word_index + 2, {{kId2, 2, 4}});
+
+  int last_word_index = sentence1.length() + sentence2.find(word8);
+  ExpectHighlightAtIndexMatches(last_word_index, {{kId2, 4, 8}});
+  ExpectHighlightAtIndexMatches(last_word_index + 2, {{kId2, 6, 8}});
+
+  // Boundary testing.
+  ExpectHighlightAtIndexEmpty(-5);
+  ExpectHighlightAtIndexEmpty(sentence1.length() + sentence2.length());
+  ExpectHighlightAtIndexEmpty(sentence1.length() + sentence2.length() + 1);
+}
+
+TEST_F(
+    ReadAnythingReadAloudAppModelV8SegmentationTest,
+    GetCurrentTextSegments_NodeSpansMultipleSentences_ReturnsCorrectSegments) {
+  std::u16string segment1 = u"The scars are part of me! ";
+  std::u16string segment2 = u"Darkness and harmony. ";
+  std::u16string segment3 = u"My voice without the lies. ";
+  std::u16string segment4 = u"This is what it sounds ";
+  std::u16string node1_text = segment1 + segment2 + segment3 + segment4;
+  std::u16string node2_text = u"like.";
+  int end1 = segment1.size();
+  int end2 = end1 + segment2.size();
+  int end3 = end2 + segment3.size();
+  int end4 = end3 + segment4.size();
+  ui::AXNodeData static_text1 = test::TextNode(kId1, node1_text);
+  ui::AXNodeData static_text2 = test::TextNode(kId2, node2_text);
+  const std::set<ui::AXNodeID> current_nodes = InitializeWithAndProcessNodes(
+      {std::move(static_text1), std::move(static_text2)});
+
+  // The first sentence is all of segment1.
+  std::vector<ReadAloudTextSegment> segments =
+      model().GetCurrentTextSegments(false, false, &current_nodes);
+  EXPECT_EQ(1u, segments.size());
+  EXPECT_EQ(kId1, segments.at(0).id);
+  EXPECT_EQ(0u, segments.at(0).text_start);
+  EXPECT_EQ(end1, segments.at(0).text_end);
+
+  // Next sentence is all of segment2.
+  MoveToNextGranularityAndGetText(&current_nodes);
+  segments = model().GetCurrentTextSegments(false, false, &current_nodes);
+  EXPECT_EQ(1u, segments.size());
+  EXPECT_EQ(kId1, segments.at(0).id);
+  EXPECT_EQ(end1, segments.at(0).text_start);
+  EXPECT_EQ(end2, segments.at(0).text_end);
+
+  // Next sentence is all of segment3.
+  MoveToNextGranularityAndGetText(&current_nodes);
+  segments = model().GetCurrentTextSegments(false, false, &current_nodes);
+  EXPECT_EQ(1u, segments.size());
+  EXPECT_EQ(kId1, segments.at(0).id);
+  EXPECT_EQ(end2, segments.at(0).text_start);
+  EXPECT_EQ(end3, segments.at(0).text_end);
+
+  // Final sentence is all of segment4 in node1 plus all of node2.
+  MoveToNextGranularityAndGetText(&current_nodes);
+  segments = model().GetCurrentTextSegments(false, false, &current_nodes);
+  EXPECT_EQ(2u, segments.size());
+  EXPECT_EQ(kId1, segments.at(0).id);
+  EXPECT_EQ(end3, segments.at(0).text_start);
+  EXPECT_EQ(end4, segments.at(0).text_end);
+  EXPECT_EQ(kId2, segments.at(1).id);
+  EXPECT_EQ(0, segments.at(1).text_start);
+  EXPECT_EQ(node2_text.size(), segments.at(1).text_end);
+}
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index ca6dbe0..35dd0c2f 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -1445,6 +1445,10 @@
           autofill::features::kAutofillPolicyControlledFeatureAutofill)) {
     blink::WebRuntimeFeatures::EnableAutofill(true);
   }
+  if (base::FeatureList::IsEnabled(
+          autofill::features::kAutofillPolicyControlledFeatureManualText)) {
+    blink::WebRuntimeFeatures::EnableManualText(true);
+  }
 
   if (base::FeatureList::IsEnabled(subresource_filter::kAdTagging))
     blink::WebRuntimeFeatures::EnableAdTagging(true);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 3eecd71..4ffddd6f 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3096,6 +3096,7 @@
 
     if (is_mac) {
       deps += [ "//device/fido:icloud_keychain_test_support" ]
+      deps += [ "//chrome/app_shim:browser_tests" ]
     }
 
     if (enable_background_mode) {
@@ -3694,7 +3695,6 @@
       "../browser/ui/views/hats/hats_browsertest.cc",
       "../browser/ui/views/incognito_clear_browsing_data_dialog_browsertest.cc",
       "../browser/ui/views/infobars/confirm_infobar_custom_layout_browsertest.cc",
-      "../browser/ui/views/infobars/infobar_view_browsertest.cc",
       "../browser/ui/views/intent_picker_bubble_view_browsertest.cc",
       "../browser/ui/views/intent_picker_dialog_browsertest.cc",
       "../browser/ui/views/location_bar/intent_chip_button_browsertest.cc",
@@ -4050,6 +4050,7 @@
       "//chrome/browser/ui/toolbar",
       "//chrome/browser/ui/toolbar/chrome_labs",
       "//chrome/browser/ui/toolbar/pinned_toolbar",
+      "//chrome/browser/ui/views/infobars:browser_tests",
       "//chrome/browser/ui/views/location_bar",
       "//chrome/browser/ui/views/location_bar/cookie_controls:browser_tests",
       "//chrome/browser/ui/views/tabs/vertical",
@@ -4823,10 +4824,7 @@
       }
 
       if (is_chromeos) {
-        deps += [
-          "//chrome/browser/ui/webui/ash/parent_access:test_support",
-          "//chromeos/ash/components/network/portal_detector:test_support",
-        ]
+        deps += [ "//chrome/browser/ui/webui/ash/parent_access:test_support" ]
         sources += [
           "../browser/extensions/api/identity/launch_web_auth_flow_delegate_ash_browsertest.cc",
           "../browser/ui/views/extensions/web_file_handlers/web_file_handlers_file_launch_browsertest.cc",
@@ -5671,7 +5669,6 @@
         "//chromeos/ash/components/login/login_state:test_support",
         "//chromeos/ash/components/login/session",
         "//chromeos/ash/components/mojo_service_manager:test_support",
-        "//chromeos/ash/components/network/portal_detector",
         "//chromeos/ash/components/osauth/public",
         "//chromeos/ash/components/osauth/test_support",
         "//chromeos/ash/components/phonehub:debug",
@@ -6018,10 +6015,7 @@
     sources += metric_integration_sources
 
     if (!is_chromeos) {
-      sources += [
-        "../browser/webauthn/change_pin_controller_impl_browsertest.cc",
-        "../browser/webauthn/enclave_authenticator_browsertest.cc",
-      ]
+      sources += [ "../browser/webauthn/enclave_authenticator_browsertest.cc" ]
       deps += [ "//chrome/browser/webauthn:test_support" ]
     }
 
@@ -10630,6 +10624,9 @@
           [ "../browser/ui/views/tabs/tab_strip_action_container_unittest.cc" ]
 
       deps += [ "//chrome/browser/ui/tabs:glic" ]
+      if (is_chromeos) {
+        deps += [ "//chrome/browser/ash/test:test_support" ]
+      }
     }
     if (is_linux) {
       sources += [
diff --git a/chrome/test/base/chrome_test_suite.cc b/chrome/test/base/chrome_test_suite.cc
index be316e5..a56c765 100644
--- a/chrome/test/base/chrome_test_suite.cc
+++ b/chrome/test/base/chrome_test_suite.cc
@@ -43,6 +43,7 @@
 #include "base/apple/scoped_nsautorelease_pool.h"
 #include "chrome/browser/app_controller_mac.h"
 #include "chrome/browser/chrome_browser_application_mac.h"
+#include "chrome/common/chrome_switches.h"
 #endif
 
 namespace {
@@ -83,7 +84,10 @@
 void ChromeTestSuite::Initialize() {
 #if BUILDFLAG(IS_MAC)
   base::apple::ScopedNSAutoreleasePool autorelease_pool;
-  chrome_browser_application_mac::RegisterBrowserCrApp();
+  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kDoNotCreateNSAppForTests)) {
+    chrome_browser_application_mac::RegisterBrowserCrApp();
+  }
 #endif
 
   if (!browser_dir_.empty()) {
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 ccd85b46..4a69996 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
@@ -42,6 +42,10 @@
   let metrics: MetricsTracker;
 
   setup(() => {
+     loadTimeData.overrideValues({
+    'composeboxImageFileTypes': 'image/avif,image/bmp,image/jpeg,image/png,image/webp,image/heif,image/heic',
+    'composeboxAttachmentFileTypes': '.pdf,application/pdf',
+  });
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
     handler = installMock(
         PageHandlerRemote,
@@ -2142,56 +2146,103 @@
             'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', 1));
   });
 
-test('upload mixed files over limit prioritizes max files error and uploads valid ones', async () => {
-  // Arrange.
-  loadTimeData.overrideValues({'composeboxFileMaxCount': 3});
-    createComposeboxElement();
+  test('upload mixed files over limit prioritizes max files error and uploads valid ones', async () => {
+    // Arrange.
+    loadTimeData.overrideValues({'composeboxFileMaxCount': 3});
+      createComposeboxElement();
 
-  let i = 0;
-    searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => {
-      i++;
-      return Promise.resolve({token: {low: BigInt(i), high: BigInt(0)}});
+    let i = 0;
+      searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => {
+        i++;
+        return Promise.resolve({token: {low: BigInt(i), high: BigInt(0)}});
+      });
+
+      const files = [
+        new File(['foo'], 'good1.png', {type: 'image/png'}),
+        new File(['foo'], 'good2.png', {type: 'image/png'}),
+        new File(['foo'], 'good3.png', {type: 'image/png'}),
+        new File(['foo'], 'bad.txt', {type: 'text/plain'}),
+      ];
+
+      const dataTransfer = new DataTransfer();
+      files.forEach(file => dataTransfer.items.add(file));
+
+      const pasteEvent = new ClipboardEvent('paste', {
+        clipboardData: dataTransfer,
+        bubbles: true,
+        cancelable: true,
+        composed: true,
+      });
+
+      const errorEventPromise =
+          eventToPromise('on-file-validation-error', composeboxElement.$.context);
+
+      // Act.
+      composeboxElement.$.input.dispatchEvent(pasteEvent);
+
+      await waitForAddFileCallCount(3);
+      await microtasksFinished();
+
+      // Assert.
+      assertEquals(3, composeboxElement.$.context.$.carousel.files.length);
+
+      const errorEvent = await errorEventPromise;
+      assertEquals(
+          loadTimeData.getString('maxFilesReachedError'),
+          errorEvent.detail.errorMessage);
+
+      assertEquals(
+          1,
+          metrics.count(
+              'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', 1));
     });
 
-    const files = [
-      new File(['foo'], 'good1.png', {type: 'image/png'}),
-      new File(['foo'], 'good2.png', {type: 'image/png'}),
-      new File(['foo'], 'good3.png', {type: 'image/png'}),
-      new File(['foo'], 'bad.txt', {type: 'text/plain'}),
-    ];
+  test(
+      'uploading valid heif and invalid svg adds valid file and shows error',
+      async () => {
+        createComposeboxElement();
 
-    const dataTransfer = new DataTransfer();
-    files.forEach(file => dataTransfer.items.add(file));
 
-    const pasteEvent = new ClipboardEvent('paste', {
-      clipboardData: dataTransfer,
-      bubbles: true,
-      cancelable: true,
-      composed: true,
-    });
+        let i = 0;
+        searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => {
+          i++;
+          return Promise.resolve({token: {low: BigInt(i), high: BigInt(0)}});
+        });
 
-    const errorEventPromise =
-        eventToPromise('on-file-validation-error', composeboxElement.$.context);
+        const validFile = new File(['foo'], 'image.png', {type: 'image/png'});
+        const invalidFile =
+            new File(['bar'], 'icon.svg', {type: 'image/svg+xml'});
 
-    // Act.
-    composeboxElement.$.input.dispatchEvent(pasteEvent);
+        const dataTransfer = new DataTransfer();
+        dataTransfer.items.add(validFile);
+        dataTransfer.items.add(invalidFile);
 
-    await waitForAddFileCallCount(3);
-    await microtasksFinished();
+        const pasteEvent = new ClipboardEvent('paste', {
+          clipboardData: dataTransfer,
+          bubbles: true,
+          cancelable: true,
+          composed: true,
+        });
 
-    // Assert.
-    assertEquals(3, composeboxElement.$.context.$.carousel.files.length);
+        const errorEventPromise = eventToPromise(
+            'on-file-validation-error', composeboxElement.$.context);
 
-    const errorEvent = await errorEventPromise;
-    assertEquals(
-        loadTimeData.getString('maxFilesReachedError'),
-        errorEvent.detail.errorMessage);
+        composeboxElement.$.input.dispatchEvent(pasteEvent);
 
-    assertEquals(
-        1,
-        metrics.count(
-            'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', 1));
-  });
+        await waitForAddFileCallCount(1);
+        await microtasksFinished();
+
+        assertEquals(1, composeboxElement.$.context.$.carousel.files.length);
+        assertEquals(
+
+            'image.png', composeboxElement.$.context.$.carousel.files[0]!.name);
+
+        const errorEvent = await errorEventPromise;
+
+        assertEquals(
+            loadTimeData.getString('composeFileTypesAllowedError'),
+            errorEvent.detail.errorMessage);
+      });
 
   test('isCollapsible attribute sets expanding state when true', async () => {
     createComposeboxElement();
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_aloud_model_test.ts b/chrome/test/data/webui/side_panel/read_anything/read_aloud_model_test.ts
index 86a06d9..aef4d171 100644
--- a/chrome/test/data/webui/side_panel/read_anything/read_aloud_model_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/read_aloud_model_test.ts
@@ -1550,4 +1550,425 @@
         expectHighlightAtIndexMatchesEmpty(535);
         expectHighlightAtIndexMatchesEmpty(-10);
       });
+
+  test('getCurrentTextSegments returns correct segments', async () => {
+    const p = document.createElement('p');
+    p.textContent = 'I broke into a million pieces';
+    document.body.appendChild(p);
+    await microtasksFinished();
+    getReadAloudModel().init(ReadAloudNode.create(document.body)!);
+
+    const segments = getReadAloudModel().getCurrentTextSegments();
+    assertEquals(1, segments.length);
+    assertEquals(p.firstChild, segments[0]!.node.domNode());
+    assertEquals(0, segments[0]!.start);
+    assertEquals(p.textContent.length, segments[0]!.length);
+  });
+
+  test(
+      'getCurrentTextSegments sentence spans multiple nodes returns correct segments',
+      async () => {
+        const div = document.createElement('div');
+        const sentence1 = document.createTextNode('and I can\'t go back, ');
+        const sentence2 =
+            document.createTextNode('But now I\'m seeing all the beauty ');
+        const sentence3 = document.createTextNode('in the broken glass.');
+        div.appendChild(sentence1);
+        div.appendChild(sentence2);
+        div.appendChild(sentence3);
+        document.body.appendChild(div);
+        await microtasksFinished();
+        getReadAloudModel().init(ReadAloudNode.create(document.body)!);
+
+        const segments = getReadAloudModel().getCurrentTextSegments();
+        assertEquals(3, segments.length);
+        assertEquals(sentence1, segments[0]!.node.domNode());
+        assertEquals(0, segments[0]!.start);
+        assertEquals(sentence1.textContent.length, segments[0]!.length);
+        assertEquals(sentence2, segments[1]!.node.domNode());
+        assertEquals(0, segments[1]!.start);
+        assertEquals(sentence2.textContent.length, segments[1]!.length);
+        assertEquals(sentence3, segments[2]!.node.domNode());
+        assertEquals(0, segments[2]!.start);
+        assertEquals(sentence3.textContent.length, segments[2]!.length);
+      });
+
+  test(
+      'getCurrentTextSegments node spans multiple sentences returns correct segments',
+      async () => {
+        const segment1 = 'The scars are part of me! ';
+        const segment2 = 'Darkness and harmony. ';
+        const segment3 = 'My voice without the lies. ';
+        const segment4 = 'This is what it sounds ';
+        const node1Text = segment1 + segment2 + segment3 + segment4;
+        const node2Text = 'like.';
+        const div = document.createElement('div');
+        const node1 = document.createTextNode(node1Text);
+        div.appendChild(node1);
+        const node2 = document.createTextNode(node2Text);
+        div.appendChild(node2);
+        document.body.appendChild(div);
+        await microtasksFinished();
+        getReadAloudModel().init(ReadAloudNode.create(document.body)!);
+
+        // Sentence 1
+        assertEquals(
+            segment1.trim(),
+            getReadAloudModel().getCurrentTextContent().trim());
+        let segments = getReadAloudModel().getCurrentTextSegments();
+        assertEquals(1, segments.length);
+        assertEquals(node1, segments[0]!.node.domNode());
+        assertEquals(0, segments[0]!.start);
+        assertEquals(segment1.length, segments[0]!.length);
+
+        // Sentence 2
+        getReadAloudModel().moveSpeechForward();
+        assertEquals(
+            segment2.trim(),
+            getReadAloudModel().getCurrentTextContent().trim());
+        segments = getReadAloudModel().getCurrentTextSegments();
+        assertEquals(1, segments.length);
+        assertEquals(node1, segments[0]!.node.domNode());
+        assertEquals(segment1.length, segments[0]!.start);
+        assertEquals(segment2.length, segments[0]!.length);
+
+        // Sentence 3
+        getReadAloudModel().moveSpeechForward();
+        assertEquals(
+            segment3.trim(),
+            getReadAloudModel().getCurrentTextContent().trim());
+        segments = getReadAloudModel().getCurrentTextSegments();
+        assertEquals(1, segments.length);
+        assertEquals(node1, segments[0]!.node.domNode());
+        assertEquals(segment1.length + segment2.length, segments[0]!.start);
+        assertEquals(segment3.length, segments[0]!.length);
+
+        // Sentence 4
+        getReadAloudModel().moveSpeechForward();
+        assertEquals(
+            (segment4 + node2Text).trim(),
+            getReadAloudModel().getCurrentTextContent().trim());
+        segments = getReadAloudModel().getCurrentTextSegments();
+        assertEquals(2, segments.length);
+        assertEquals(node1, segments[0]!.node.domNode());
+        assertEquals(
+            segment1.length + segment2.length + segment3.length,
+            segments[0]!.start);
+        assertEquals(segment4.length, segments[0]!.length);
+        assertEquals(node2, segments[1]!.node.domNode());
+        assertEquals(0, segments[1]!.start);
+        assertEquals(node2Text.length, segments[1]!.length);
+      });
+
+  test(
+      'getCurrentTextSegments after previous returns correct nodes',
+      async () => {
+        const sentence1 =
+            'Why did I cover up the colors stuck inside my head? ';
+        const sentence2 = 'I should\'ve let the jagged edges.';
+        const sentence3 = 'meet the light instead.';
+        const p1 = document.createElement('p');
+        p1.textContent = sentence1;
+        const p2 = document.createElement('p');
+        p2.textContent = sentence2;
+        const p3 = document.createElement('p');
+        p3.textContent = sentence3;
+        document.body.appendChild(p1);
+        document.body.appendChild(p2);
+        document.body.appendChild(p3);
+        await microtasksFinished();
+        getReadAloudModel().init(ReadAloudNode.create(document.body)!);
+
+        getReadAloudModel().moveSpeechForward();
+        getReadAloudModel().moveSpeechForward();
+        assertEquals(
+            sentence3, getReadAloudModel().getCurrentTextContent().trim());
+
+        getReadAloudModel().moveSpeechBackwards();
+        assertEquals(
+            sentence2, getReadAloudModel().getCurrentTextContent().trim());
+        const segments = getReadAloudModel().getCurrentTextSegments();
+        assertEquals(1, segments.length);
+        assertEquals(p2.firstChild, segments[0]!.node.domNode());
+        assertEquals(0, segments[0]!.start);
+        assertEquals(sentence2.length, segments[0]!.length);
+      });
+
+  test(
+      'getHighlightForCurrentSegmentIndex after moving forward returns correct nodes',
+      async () => {
+        const sentence1 = 'Never feel heavy or earthbound. ';
+        const sentence2 = 'No worries or doubts ';
+        const sentence3 = 'interfere.';
+        const paragraph = document.createElement('p');
+        const child1 = document.createTextNode(sentence1);
+        const child2 = document.createTextNode(sentence2);
+        const child3 = document.createTextNode(sentence3);
+        paragraph.appendChild(child1);
+        paragraph.appendChild(child2);
+        paragraph.appendChild(child3);
+        document.body.appendChild(paragraph);
+        await microtasksFinished();
+        getReadAloudModel().init(ReadAloudNode.create(document.body)!);
+
+        // Spot check that indices 0->sentence1.length() map to the first node
+        // id.
+        expectHighlightAtIndexMatches(0, [{node: child1, start: 0, length: 5}]);
+        expectHighlightAtIndexMatches(7, [{node: child1, start: 7, length: 3}]);
+        expectHighlightAtIndexMatchesEmpty(sentence1.length - 1);
+        expectHighlightAtIndexMatchesEmpty(sentence1.length);
+
+        // Move to the next granularity.
+        getReadAloudModel().moveSpeechForward();
+        assertEquals(
+            (sentence2 + sentence3).trim(),
+            getReadAloudModel().getCurrentTextContent().trim());
+
+        // Spot check that indices in sentence 2 map to the second node id.
+        expectHighlightAtIndexMatches(0, [{node: child2, start: 0, length: 2}]);
+        expectHighlightAtIndexMatches(7, [{node: child2, start: 7, length: 3}]);
+        expectHighlightAtIndexMatches(
+            sentence2.length, [{node: child3, start: 0, length: 9}]);
+
+        // Spot check that indices in sentence 3 map to the third node id.
+        expectHighlightAtIndexMatches(
+            sentence2.length + 1, [{node: child3, start: 1, length: 8}]);
+        expectHighlightAtIndexMatches(
+            27, [{node: child3, start: 6, length: 3}]);
+        expectHighlightAtIndexMatchesEmpty(
+            sentence2.length + sentence3.length - 1);
+
+        // Out-of-bounds nodes return invalid.
+        expectHighlightAtIndexMatchesEmpty(
+            sentence2.length + sentence3.length + 1);
+      });
+
+  test(
+      'getHighlightForCurrentSegmentIndex after backwards returns correct highlight',
+      async () => {
+        const sentence1 = 'There\'s nothing but you ';
+        const sentence2 = 'looking down on the view from up here. ';
+        const sentence3 = 'Stretch out with the wind behind you.';
+        const paragraph = document.createElement('p');
+        const child1 = document.createTextNode(sentence1);
+        const child2 = document.createTextNode(sentence2);
+        const child3 = document.createTextNode(sentence3);
+        paragraph.appendChild(child1);
+        paragraph.appendChild(child2);
+        paragraph.appendChild(child3);
+
+        document.body.appendChild(paragraph);
+        await microtasksFinished();
+
+        // Before there are any processed granularities, there should be no
+        // highlights.
+        expectHighlightAtIndexMatchesEmpty(1);
+        getReadAloudModel().init(ReadAloudNode.create(document.body)!);
+
+        getReadAloudModel().moveSpeechForward();
+        // Spot check that indices 0->sentence3.length() map to the third node
+        // id.
+        expectHighlightAtIndexMatches(0, [{node: child3, start: 0, length: 7}]);
+        expectHighlightAtIndexMatches(7, [{node: child3, start: 7, length: 4}]);
+        expectHighlightAtIndexMatchesEmpty(sentence3.length - 1);
+
+        getReadAloudModel().moveSpeechBackwards();
+        assertEquals(
+            (sentence1 + sentence2).trim(),
+            getReadAloudModel().getCurrentTextContent().trim());
+
+        // Spot check that indices in sentence 1 map to the first node id.
+        expectHighlightAtIndexMatches(0, [{node: child1, start: 0, length: 7}]);
+        expectHighlightAtIndexMatches(6, [{node: child1, start: 6, length: 1}]);
+        expectHighlightAtIndexMatches(sentence1.length - 1, [
+          {node: child1, start: 23, length: 1},
+          {node: child2, start: 0, length: 7},
+        ]);
+
+        // Spot check that indices in sentence 2 map to the second node id.
+        expectHighlightAtIndexMatches(
+            sentence1.length + 1, [{node: child2, start: 1, length: 6}]);
+        expectHighlightAtIndexMatches(
+            27, [{node: child2, start: 3, length: 4}]);
+        expectHighlightAtIndexMatchesEmpty(
+            sentence1.length + sentence2.length - 1);
+
+        // Out-of-bounds nodes return invalid.
+        expectHighlightAtIndexMatchesEmpty(
+            sentence1.length + sentence2.length + 1);
+      });
+
+  test(
+      'getHighlightForCurrentSegmentIndex multinode words returns correct length',
+      async () => {
+        const word1 = 'Stretch ';
+        const word2 = 'out ';
+        const word3 = 'with ';
+        const word4 = 'the ';
+        const word5 = 'wind ';
+        const word6 = 'beh';
+        const word7 = 'ind ';
+        const word8 = 'you.';
+        const sentence1 = word1 + word2 + word3 + word4 + word5 + word6;
+        const sentence2 = word7 + word8;
+        const p = document.createElement('p');
+        const node1 = document.createTextNode(sentence1);
+        const node2 = document.createTextNode(sentence2);
+        p.appendChild(node1);
+        p.appendChild(node2);
+        document.body.appendChild(p);
+        await microtasksFinished();
+
+        getReadAloudModel().init(ReadAloudNode.create(document.body)!);
+        assertEquals(
+            'Stretch out with the wind behind you.',
+            getReadAloudModel().getCurrentTextContent().trim());
+
+        // Throughout first word.
+        expectHighlightAtIndexMatches(0, [{node: node1, start: 0, length: 7}]);
+        expectHighlightAtIndexMatches(2, [{node: node1, start: 2, length: 5}]);
+        expectHighlightAtIndexMatches(
+            word1.length - 2, [{node: node1, start: 6, length: 1}]);
+
+        // Throughout third word.
+        const thirdWordIndex: number = sentence1.indexOf(word3);
+        expectHighlightAtIndexMatches(
+            thirdWordIndex, [{node: node1, start: 12, length: 4}]);
+        expectHighlightAtIndexMatches(
+            thirdWordIndex + 2, [{node: node1, start: 14, length: 2}]);
+
+        // Words split across node boundaries
+        const sixthWordIndex: number = sentence1.indexOf(word6);
+        expectHighlightAtIndexMatches(sixthWordIndex, [
+          {node: node1, start: 26, length: 3},
+          {node: node2, start: 0, length: 3},
+        ]);
+        expectHighlightAtIndexMatches(sixthWordIndex + 2, [
+          {node: node1, start: 28, length: 1},
+          {node: node2, start: 0, length: 3},
+        ]);
+
+        const seventhWordIndex: number = sentence1.length;
+        expectHighlightAtIndexMatches(
+            seventhWordIndex, [{node: node2, start: 0, length: 3}]);
+        expectHighlightAtIndexMatches(
+            seventhWordIndex + 2, [{node: node2, start: 2, length: 1}]);
+
+        const lastWordIndex = sentence1.length + sentence2.indexOf(word8);
+        expectHighlightAtIndexMatches(
+            lastWordIndex, [{node: node2, start: 4, length: 3}]);
+        expectHighlightAtIndexMatches(
+            lastWordIndex + 2, [{node: node2, start: 6, length: 1}]);
+
+        // Boundary testing.
+        expectHighlightAtIndexMatchesEmpty(-5);
+        expectHighlightAtIndexMatchesEmpty(sentence1.length + sentence2.length);
+        expectHighlightAtIndexMatchesEmpty(
+            sentence1.length + sentence2.length + 1);
+      });
+
+  test(
+      'getHighlightForCurrentSegmentIndex node spans multiple sentences returns correct nodes',
+      async () => {
+        const segment1 = 'I\'m taking what\'s mine! ';
+        const segment2 = 'Every drop, every smidge. ';
+        const segment3 = 'If I\'m burning a bridge, let it burn. ';
+        const segment4 = 'But I\'m crossing the ';
+        const node1Text = segment1 + segment2 + segment3 + segment4;
+        const node2Text = 'line.';
+
+        const div = document.createElement('div');
+        const node1 = document.createTextNode(node1Text);
+        div.appendChild(node1);
+        const node2 = document.createTextNode(node2Text);
+        div.appendChild(node2);
+        document.body.appendChild(div);
+        await microtasksFinished();
+        getReadAloudModel().init(ReadAloudNode.create(document.body)!);
+
+        // First sentence
+        assertEquals(
+            segment1.trim(),
+            getReadAloudModel().getCurrentTextContent().trim());
+        expectHighlightAtIndexMatches(0, [{node: node1, start: 0, length: 3}]);
+        expectHighlightAtIndexMatches(6, [{node: node1, start: 6, length: 4}]);
+        expectHighlightAtIndexMatches(
+            15, [{node: node1, start: 15, length: 2}]);
+        expectHighlightAtIndexMatchesEmpty(segment1.length - 1);
+        expectHighlightAtIndexMatchesEmpty(segment1.length);
+
+        // Move to segment 2.
+        getReadAloudModel().moveSpeechForward();
+        assertEquals(
+            segment2.trim(),
+            getReadAloudModel().getCurrentTextContent().trim());
+
+        // For the second segment, the boundary index will have reset for the
+        // new speech segment. The correct highlight start index is the index
+        // that the boundary index within the segment corresponds to within the
+        // node.
+        let baseLength: number = segment1.length;
+        expectHighlightAtIndexMatches(
+            0, [{node: node1, start: baseLength, length: 5}]);
+        expectHighlightAtIndexMatches(
+            10, [{node: node1, start: baseLength + 10, length: 7}]);
+        expectHighlightAtIndexMatches(
+            13, [{node: node1, start: baseLength + 13, length: 4}]);
+
+        baseLength += segment2.length;
+        expectHighlightAtIndexMatchesEmpty(segment2.length - 1);
+        expectHighlightAtIndexMatchesEmpty(segment1.length + segment2.length);
+
+        // Move to segment 3.
+        getReadAloudModel().moveSpeechForward();
+        assertEquals(
+            segment3.trim(),
+            getReadAloudModel().getCurrentTextContent().trim());
+
+        // For the third segment, the boundary index will have reset for the new
+        // speech segment. The correct highlight start index is the index that
+        // the boundary index within the segment corresponds to within the node.
+        expectHighlightAtIndexMatches(
+            0, [{node: node1, start: baseLength, length: 2}]);
+        expectHighlightAtIndexMatches(
+            9, [{node: node1, start: baseLength + 9, length: 5}]);
+        expectHighlightAtIndexMatches(
+            13, [{node: node1, start: baseLength + 13, length: 1}]);
+        expectHighlightAtIndexMatchesEmpty(segment3.length - 1);
+        expectHighlightAtIndexMatchesEmpty(baseLength + segment3.length - 1);
+
+        // Move to segment 4.
+        getReadAloudModel().moveSpeechForward();
+        assertEquals(
+            segment4 + node2Text,
+            getReadAloudModel().getCurrentTextContent().trim());
+
+        // For the fourth segment, there are two nodes. For the first node,
+        // the correct highlight start corresponds to the index within the first
+        // node.
+        baseLength += segment3.length;
+        expectHighlightAtIndexMatches(
+            0, [{node: node1, start: baseLength, length: 3}]);
+        expectHighlightAtIndexMatches(
+            2, [{node: node1, start: baseLength + 2, length: 1}]);
+        expectHighlightAtIndexMatches(
+            8, [{node: node1, start: baseLength + 8, length: 8}]);
+        expectHighlightAtIndexMatches(segment4.length - 1, [
+          {node: node1, start: baseLength + segment4.length - 1, length: 1},
+          {node: node2, start: 0, length: 4},
+        ]);
+
+
+        // For the second node, the highlight index corresponds to the position
+        // within the second node.
+        expectHighlightAtIndexMatches(
+            segment4.length, [{node: node2, start: 0, length: 4}]);
+        expectHighlightAtIndexMatches(
+            segment4.length + 2, [{node: node2, start: 2, length: 2}]);
+        expectHighlightAtIndexMatches(
+            (segment4 + node2Text).length - 2,
+            [{node: node2, start: node2Text.length - 2, length: 1}]);
+        expectHighlightAtIndexMatchesEmpty((segment4 + node2Text).length - 1);
+        expectHighlightAtIndexMatchesEmpty((segment4 + node2Text).length);
+      });
 });
diff --git a/chrome/updater/app/app_server_win.cc b/chrome/updater/app/app_server_win.cc
index 051aed44..23c6a4f6 100644
--- a/chrome/updater/app/app_server_win.cc
+++ b/chrome/updater/app/app_server_win.cc
@@ -266,8 +266,6 @@
                                     GetAppServerWinInstance();
                                 this_server->update_service_ = nullptr;
                                 this_server->update_service_internal_ = nullptr;
-                                this_server->active_duty_stub_.reset();
-                                this_server->active_duty_internal_stub_.reset();
                                 this_server->Shutdown(0);
                               }));
 }
@@ -346,11 +344,6 @@
       update_service, base::BindRepeating(&AppServerWin::TaskStarted, this),
       base::BindRepeating(&AppServerWin::TaskCompleted, this));
 
-  active_duty_stub_ = std::make_unique<UpdateServiceStub>(
-      update_service, updater_scope(),
-      base::BindRepeating(&AppServerWin::TaskStarted, this),
-      base::BindRepeating(&AppServerWin::TaskCompleted, this));
-
   Start(base::BindOnce(&AppServerWin::RegisterClassObjects,
                        base::Unretained(this)));
 }
@@ -362,11 +355,6 @@
       base::BindRepeating(&AppServerWin::TaskStarted, this),
       base::BindRepeating(&AppServerWin::TaskCompleted, this));
 
-  active_duty_internal_stub_ = std::make_unique<UpdateServiceInternalStub>(
-      update_service_internal, updater_scope(),
-      base::BindRepeating(&AppServerWin::TaskStarted, this),
-      base::BindRepeating(&AppServerWin::TaskCompleted, this));
-
   Start(base::BindOnce(&AppServerWin::RegisterInternalClassObjects,
                        base::Unretained(this)));
 }
diff --git a/chrome/updater/app/app_server_win.h b/chrome/updater/app/app_server_win.h
index 5488ae7..991cb41 100644
--- a/chrome/updater/app/app_server_win.h
+++ b/chrome/updater/app/app_server_win.h
@@ -7,8 +7,6 @@
 
 #include <windows.h>
 
-#include <memory>
-
 #include "base/check.h"
 #include "base/functional/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
@@ -19,9 +17,6 @@
 
 namespace updater {
 
-class UpdateServiceInternalStub;
-class UpdateServiceStub;
-
 // Returns S_OK if user install, or if the COM caller is admin. Error otherwise.
 HRESULT IsCOMCallerAllowed();
 
@@ -104,8 +99,6 @@
   // |update_client| component.
   scoped_refptr<UpdateService> update_service_;
   scoped_refptr<UpdateServiceInternal> update_service_internal_;
-  std::unique_ptr<UpdateServiceInternalStub> active_duty_internal_stub_;
-  std::unique_ptr<UpdateServiceStub> active_duty_stub_;
 };
 
 // Returns the singleton AppServerWin instance.
diff --git a/chrome/updater/net/network_fetcher_win.cc b/chrome/updater/net/network_fetcher_win.cc
index 8309ad60..73bbf17 100644
--- a/chrome/updater/net/network_fetcher_win.cc
+++ b/chrome/updater/net/network_fetcher_win.cc
@@ -43,33 +43,30 @@
 namespace updater {
 namespace {
 
+decltype(&GetNetworkConnectivityHint) GetGetNetworkConnectivityHint() {
+  HMODULE hmod = LoadLibraryW(L"IPHLPAPI.DLL");
+  if (!hmod) {
+    return nullptr;
+  }
+  // GetNetworkConnectivityHint is not present on Windows < 19041 so this can
+  // return nullptr on failure on older Windows versions.
+  return reinterpret_cast<decltype(&GetNetworkConnectivityHint)>(
+      GetProcAddress(hmod, "GetNetworkConnectivityHint"));
+}
+
 std::optional<int> GetNetworkConnectivityCostHint() {
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                 base::BlockingType::MAY_BLOCK);
-  using GetNetworkConnectivityHint =
-      NTSTATUS(WINAPI*)(NL_NETWORK_CONNECTIVITY_HINT*);
-  static const HMODULE iphlpapi_handle = ::LoadLibrary(L"Iphlpapi.dll");
-  if (!iphlpapi_handle) {
-    VLOG(1) << "Failed to LoadLibrary Iphlpapi.dll";
-    return std::nullopt;
-  }
-  static const HMODULE iphlpapi_module_handle =
-      ::GetModuleHandle(L"Iphlpapi.dll");
-  if (!iphlpapi_module_handle) {
-    VLOG(1) << "Failed to GetModuleHandle Iphlpapi.dll";
-    return std::nullopt;
-  }
-  static const GetNetworkConnectivityHint get_network_connectivity_hint =
-      reinterpret_cast<GetNetworkConnectivityHint>(::GetProcAddress(
-          iphlpapi_module_handle, "GetNetworkConnectivityHint"));
-  if (!get_network_connectivity_hint) {
-    VLOG(1) << "Failed to GetProcAddress for GetNetworkConnectivityHint";
+  static auto get_network_connectivity_hint_fn =
+      GetGetNetworkConnectivityHint();
+  if (!get_network_connectivity_hint_fn) {
+    VLOG(1) << "Failed to lookup GetNetworkConnectivityHint";
     return std::nullopt;
   }
 
   NL_NETWORK_CONNECTIVITY_HINT connectivity_hint{
       .ConnectivityCost = ::NetworkConnectivityCostHintUnknown};
-  NTSTATUS status = get_network_connectivity_hint(&connectivity_hint);
+  NTSTATUS status = get_network_connectivity_hint_fn(&connectivity_hint);
   if (status != NO_ERROR) {
     VLOG(1) << "GetNetworkConnectivityHint failed with status " << status;
     return std::nullopt;
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index b4ed539e..bfc3d37a 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -1497,7 +1497,8 @@
 }
 
 #if BUILDFLAG(IS_WIN)
-TEST_F(IntegrationTest, CheckForUpdateAndInstallAppViaMojo) {
+// TODO(crbug.com/462797181): Disabled while mojo server is disabled.
+TEST_F(IntegrationTest, DISABLED_CheckForUpdateAndInstallAppViaMojo) {
   ScopedServer test_server(test_commands_);
   ExpectInstallEvent(test_server, kUpdaterAppId);
   ASSERT_NO_FATAL_FAILURE(Install());
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index dcb94b0..76dffcb 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-16494.0.0-1073371
\ No newline at end of file
+16495.0.0-1073391
\ No newline at end of file
diff --git a/chromeos/ash/components/boca/receiver/BUILD.gn b/chromeos/ash/components/boca/receiver/BUILD.gn
index 4a1a6eb..2a18e925 100644
--- a/chromeos/ash/components/boca/receiver/BUILD.gn
+++ b/chromeos/ash/components/boca/receiver/BUILD.gn
@@ -12,6 +12,8 @@
     "get_kiosk_receiver_request.h",
     "get_receiver_connection_info_request.cc",
     "get_receiver_connection_info_request.h",
+    "receiver_connection_info_poller.cc",
+    "receiver_connection_info_poller.h",
     "receiver_handler_delegate.h",
     "register_receiver_request.cc",
     "register_receiver_request.h",
@@ -29,6 +31,7 @@
 
   deps = [
     ":util",
+    "//ash/constants",
     "//base",
     "//chromeos/ash/components/boca",
     "//chromeos/ash/components/boca/proto",
@@ -63,6 +66,7 @@
     "get_kiosk_receiver_request_unittest.cc",
     "get_receiver_connection_info_request_unittest.cc",
     "kiosk_receiver_parser_unittest.cc",
+    "receiver_connection_info_poller_unittest.cc",
     "register_receiver_request_unittest.cc",
     "start_kiosk_receiver_request_unittest.cc",
     "student_screen_presenter_impl_unittest.cc",
@@ -73,6 +77,7 @@
   deps = [
     ":receiver",
     ":util",
+    "//ash/constants",
     "//base",
     "//base/test:test_support",
     "//chromeos/ash/components/boca",
@@ -82,6 +87,7 @@
     "//components/signin/public/identity_manager:test_support",
     "//google_apis",
     "//google_apis/common",
+    "//net/traffic_annotation:test_support",
     "//services/network:test_support",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/chromeos/ash/components/boca/receiver/receiver_connection_info_poller.cc b/chromeos/ash/components/boca/receiver/receiver_connection_info_poller.cc
new file mode 100644
index 0000000..7e8f5b4
--- /dev/null
+++ b/chromeos/ash/components/boca/receiver/receiver_connection_info_poller.cc
@@ -0,0 +1,117 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/ash/components/boca/receiver/receiver_connection_info_poller.h"
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "ash/constants/ash_features.h"
+#include "base/functional/bind.h"
+#include "base/task/bind_post_task.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "chromeos/ash/components/boca/boca_request.h"
+#include "chromeos/ash/components/boca/proto/receiver.pb.h"
+#include "chromeos/ash/components/boca/receiver/get_receiver_connection_info_request.h"
+#include "chromeos/ash/components/boca/retriable_request_sender.h"
+#include "google_apis/common/request_sender.h"
+
+namespace ash::boca_receiver {
+namespace {
+
+base::TimeDelta GetPollingInterval() {
+  constexpr base::TimeDelta kDefaultPollingInterval = base::Seconds(10);
+  return ash::features::IsBocaReceiverCustomPollingEnabled()
+             ? ash::features::kBocaReceiverCustomPollingInterval.Get()
+             : kDefaultPollingInterval;
+}
+
+int GetMaxConsecutiveFailures() {
+  constexpr int kDefaultMaxConsecutiveFailures = 3;
+  return ash::features::IsBocaReceiverCustomPollingEnabled()
+             ? ash::features::kBocaReceiverCustomPollingMaxFailuresCount.Get()
+             : kDefaultMaxConsecutiveFailures;
+}
+
+}  // namespace
+
+ReceiverConnectionInfoPoller::ReceiverConnectionInfoPoller() = default;
+ReceiverConnectionInfoPoller::~ReceiverConnectionInfoPoller() = default;
+
+void ReceiverConnectionInfoPoller::Start(
+    const std::string& receiver_id,
+    const std::string& connection_id,
+    std::unique_ptr<google_apis::RequestSender> request_sender,
+    OnStopCallback on_stop_callback) {
+  constexpr int kMaxRetriesPerRequest = 1;
+  Stop();
+  retriable_sender_ = std::make_unique<
+      ash::boca::RetriableRequestSender<::boca::KioskReceiverConnection>>(
+      std::move(request_sender), kMaxRetriesPerRequest);
+
+  polling_timer_.Start(
+      FROM_HERE, GetPollingInterval(),
+      base::BindOnce(&ReceiverConnectionInfoPoller::PollConnectionInfo,
+                     base::Unretained(this), receiver_id, connection_id,
+                     std::move(on_stop_callback)));
+}
+
+void ReceiverConnectionInfoPoller::Stop() {
+  polling_timer_.Stop();
+  retriable_sender_.reset();
+  consecutive_failure_count_ = 0;
+  weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
+void ReceiverConnectionInfoPoller::PollConnectionInfo(
+    const std::string& receiver_id,
+    const std::string& connection_id,
+    OnStopCallback on_stop_callback) {
+  auto create_delegate_callback = base::BindRepeating(
+      [](const std::string& receiver_id, const std::string& connection_id,
+         base::OnceCallback<void(
+             std::optional<::boca::KioskReceiverConnection>)> callback)
+          -> std::unique_ptr<ash::boca::BocaRequest::Delegate> {
+        return std::make_unique<
+            boca_receiver::GetReceiverConnectionInfoRequest>(
+            receiver_id, connection_id, std::move(callback));
+      },
+      receiver_id, connection_id);
+
+  retriable_sender_->SendRequest(
+      create_delegate_callback,
+      base::BindPostTaskToCurrentDefault(
+          base::BindOnce(&ReceiverConnectionInfoPoller::OnConnectionInfoPolled,
+                         weak_ptr_factory_.GetWeakPtr(), receiver_id,
+                         connection_id, std::move(on_stop_callback))));
+}
+
+void ReceiverConnectionInfoPoller::OnConnectionInfoPolled(
+    const std::string& receiver_id,
+    const std::string& connection_id,
+    OnStopCallback on_stop_callback,
+    std::optional<::boca::KioskReceiverConnection> response) {
+  consecutive_failure_count_ =
+      response.has_value() ? 0 : consecutive_failure_count_ + 1;
+  bool server_unreachable =
+      consecutive_failure_count_ >= GetMaxConsecutiveFailures();
+  if (server_unreachable ||
+      (response.has_value() &&
+       response->receiver_connection_state() ==
+           ::boca::ReceiverConnectionState::STOP_REQUESTED)) {
+    std::move(on_stop_callback).Run(server_unreachable);
+    Stop();
+    return;
+  }
+  polling_timer_.Start(
+      FROM_HERE, GetPollingInterval(),
+      base::BindOnce(&ReceiverConnectionInfoPoller::PollConnectionInfo,
+                     base::Unretained(this), receiver_id, connection_id,
+                     std::move(on_stop_callback)));
+}
+
+}  // namespace ash::boca_receiver
diff --git a/chromeos/ash/components/boca/receiver/receiver_connection_info_poller.h b/chromeos/ash/components/boca/receiver/receiver_connection_info_poller.h
new file mode 100644
index 0000000..b886c86
--- /dev/null
+++ b/chromeos/ash/components/boca/receiver/receiver_connection_info_poller.h
@@ -0,0 +1,70 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_ASH_COMPONENTS_BOCA_RECEIVER_RECEIVER_CONNECTION_INFO_POLLER_H_
+#define CHROMEOS_ASH_COMPONENTS_BOCA_RECEIVER_RECEIVER_CONNECTION_INFO_POLLER_H_
+
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "base/functional/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+
+namespace ash::boca {
+template <class T>
+class RetriableRequestSender;
+}  // namespace ash::boca
+
+namespace google_apis {
+class RequestSender;
+}  // namespace google_apis
+
+namespace boca {
+class KioskReceiverConnection;
+}  // namespace boca
+
+namespace ash::boca_receiver {
+
+class ReceiverConnectionInfoPoller {
+ public:
+  using OnStopCallback = base::OnceCallback<void(bool server_unreachable)>;
+
+  ReceiverConnectionInfoPoller();
+
+  ReceiverConnectionInfoPoller(const ReceiverConnectionInfoPoller&) = delete;
+  ReceiverConnectionInfoPoller& operator=(const ReceiverConnectionInfoPoller&) =
+      delete;
+
+  ~ReceiverConnectionInfoPoller();
+
+  void Start(const std::string& receiver_id,
+             const std::string& connection_id,
+             std::unique_ptr<google_apis::RequestSender> request_sender,
+             OnStopCallback on_stop_callback);
+
+  void Stop();
+
+ private:
+  void PollConnectionInfo(const std::string& receiver_id,
+                          const std::string& connection_id,
+                          OnStopCallback on_stop_callback);
+  void OnConnectionInfoPolled(
+      const std::string& receiver_id,
+      const std::string& connection_id,
+      OnStopCallback on_stop_callback,
+      std::optional<::boca::KioskReceiverConnection> response);
+
+  std::unique_ptr<boca::RetriableRequestSender<::boca::KioskReceiverConnection>>
+      retriable_sender_;
+  base::OneShotTimer polling_timer_;
+  int consecutive_failure_count_ = 0;
+
+  base::WeakPtrFactory<ReceiverConnectionInfoPoller> weak_ptr_factory_{this};
+};
+
+}  // namespace ash::boca_receiver
+
+#endif  // CHROMEOS_ASH_COMPONENTS_BOCA_RECEIVER_RECEIVER_CONNECTION_INFO_POLLER_H_
diff --git a/chromeos/ash/components/boca/receiver/receiver_connection_info_poller_unittest.cc b/chromeos/ash/components/boca/receiver/receiver_connection_info_poller_unittest.cc
new file mode 100644
index 0000000..e4908ea
--- /dev/null
+++ b/chromeos/ash/components/boca/receiver/receiver_connection_info_poller_unittest.cc
@@ -0,0 +1,192 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/ash/components/boca/receiver/receiver_connection_info_poller.h"
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include "ash/constants/ash_features.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
+#include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "base/time/time.h"
+#include "chromeos/ash/components/boca/session_api/constants.h"
+#include "chromeos/ash/components/boca/session_api/get_session_request.h"
+#include "chromeos/ash/components/boca/util.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "google_apis/common/auth_service.h"
+#include "google_apis/common/request_sender.h"
+#include "net/http/http_status_code.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash::boca_receiver {
+namespace {
+
+constexpr char kReceiverId[] = "receiver-id";
+constexpr char kConnectionId[] = "connection-id";
+
+constexpr char kConnectedResponse[] =
+    R"({"connectionId": "connection-id",
+        "receiverConnectionState": "CONNECTED"})";
+constexpr char kStopRequestedResponse[] =
+    R"({"connectionId": "connection-id",
+        "receiverConnectionState": "STOP_REQUESTED"})";
+
+class ReceiverConnectionInfoPollerTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    identity_test_env_.MakeAccountAvailable("test@example.com");
+    identity_test_env_.SetPrimaryAccount("test@example.com",
+                                         signin::ConsentLevel::kSync);
+    identity_test_env_.SetAutomaticIssueOfAccessTokens(/*grant=*/true);
+  }
+
+  std::unique_ptr<google_apis::RequestSender> CreateRequestSender() {
+    auto auth_service = std::make_unique<google_apis::AuthService>(
+        identity_test_env_.identity_manager(),
+        identity_test_env_.identity_manager()->GetPrimaryAccountId(
+            signin::ConsentLevel::kSignin),
+        url_loader_factory_.GetSafeWeakWrapper(),
+        signin::OAuthConsumerId::kChromeOsBocaSchoolToolsAuth);
+    return std::make_unique<google_apis::RequestSender>(
+        std::move(auth_service), url_loader_factory_.GetSafeWeakWrapper(),
+        task_environment_.GetMainThreadTaskRunner(), "test-user-agent",
+        TRAFFIC_ANNOTATION_FOR_TESTS);
+  }
+
+  GURL GetConnectionInfoUrl(std::string_view connection_id) {
+    return GURL(base::StrCat(
+        {boca::GetSchoolToolsUrl(),
+         base::ReplaceStringPlaceholders(
+             "/v1/receivers/$1/kioskReceiver:getConnectionInfo?connectionId=$2",
+             {std::string(kReceiverId), std::string(connection_id)},
+             nullptr)}));
+  }
+
+  void SimulateResponse(std::string_view connection_id,
+                        std::string_view content,
+                        net::HttpStatusCode status_code = net::HTTP_OK) {
+    url_loader_factory_.SimulateResponseForPendingRequest(
+        GetConnectionInfoUrl(connection_id).spec(), content, status_code,
+        network::TestURLLoaderFactory::ResponseMatchFlags::kWaitForRequest);
+  }
+
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  signin::IdentityTestEnvironment identity_test_env_;
+  network::TestURLLoaderFactory url_loader_factory_;
+  ReceiverConnectionInfoPoller poller_;
+};
+
+TEST_F(ReceiverConnectionInfoPollerTest, StartsAndPollsSuccessfully) {
+  base::test::TestFuture<bool> stop_future;
+  poller_.Start(kReceiverId, kConnectionId, CreateRequestSender(),
+                stop_future.GetCallback());
+
+  // First poll after interval.
+  task_environment_.FastForwardBy(base::Seconds(10));
+  SimulateResponse(kConnectionId, kConnectedResponse);
+  EXPECT_FALSE(stop_future.IsReady());
+
+  // Second poll.
+  task_environment_.FastForwardBy(base::Seconds(10));
+  SimulateResponse(kConnectionId, kConnectedResponse);
+  EXPECT_FALSE(stop_future.IsReady());
+}
+
+TEST_F(ReceiverConnectionInfoPollerTest, StopsOnServerRequest) {
+  base::test::TestFuture<bool> stop_future;
+  poller_.Start(kReceiverId, kConnectionId, CreateRequestSender(),
+                stop_future.GetCallback());
+
+  task_environment_.FastForwardBy(base::Seconds(10));
+  SimulateResponse(kConnectionId, kStopRequestedResponse);
+  task_environment_.FastForwardBy(base::Seconds(10));
+
+  EXPECT_FALSE(stop_future.Get());  // server_unreachable is false.
+  EXPECT_EQ(url_loader_factory_.NumPending(), 0);
+}
+
+TEST_F(ReceiverConnectionInfoPollerTest, StopsAfterFailures) {
+  base::test::TestFuture<bool> stop_future;
+  poller_.Start(kReceiverId, kConnectionId, CreateRequestSender(),
+                stop_future.GetCallback());
+
+  // First failure (with one retry).
+  task_environment_.FastForwardBy(base::Seconds(10));
+  SimulateResponse(kConnectionId, /*content=*/"", net::HTTP_NOT_FOUND);
+  SimulateResponse(kConnectionId, /*content=*/"", net::HTTP_NOT_FOUND);
+
+  // Second failure.
+  task_environment_.FastForwardBy(base::Seconds(10));
+  SimulateResponse(kConnectionId, /*content=*/"", net::HTTP_NOT_FOUND);
+  SimulateResponse(kConnectionId, /*content=*/"", net::HTTP_NOT_FOUND);
+
+  // Third failure.
+  task_environment_.FastForwardBy(base::Seconds(10));
+  SimulateResponse(kConnectionId, /*content=*/"", net::HTTP_NOT_FOUND);
+  SimulateResponse(kConnectionId, /*content=*/"", net::HTTP_NOT_FOUND);
+
+  EXPECT_TRUE(stop_future.Get());  // server_unreachable is true.
+  EXPECT_EQ(url_loader_factory_.NumPending(), 0);
+}
+
+TEST_F(ReceiverConnectionInfoPollerTest, ExplicitStop) {
+  base::test::TestFuture<bool> stop_future;
+  task_environment_.FastForwardBy(base::Seconds(10));
+  poller_.Start(kReceiverId, kConnectionId, CreateRequestSender(),
+                stop_future.GetCallback());
+
+  task_environment_.FastForwardBy(base::Seconds(2));
+  poller_.Stop();
+  // No polling should happen.
+  task_environment_.FastForwardBy(base::Seconds(10));
+
+  EXPECT_EQ(url_loader_factory_.NumPending(), 0);
+}
+
+TEST_F(ReceiverConnectionInfoPollerTest, CustomPollingEnabled) {
+  base::test::ScopedFeatureList feature_list;
+  base::FieldTrialParams params;
+  params["BocaReceiverCustomPollingInterval"] = "5s";
+  params["BocaReceiverCustomPollingMaxFailuresCount"] = "2";
+  feature_list.InitAndEnableFeatureWithParameters(
+      ash::features::kBocaReceiverCustomPolling, params);
+
+  base::test::TestFuture<bool> stop_future;
+  poller_.Start(kReceiverId, kConnectionId, CreateRequestSender(),
+                stop_future.GetCallback());
+
+  // First poll after custom interval.
+  task_environment_.FastForwardBy(base::Seconds(5));
+  SimulateResponse(kConnectionId, kConnectedResponse);
+  EXPECT_FALSE(stop_future.IsReady());
+
+  // First failure (with one retry).
+  task_environment_.FastForwardBy(base::Seconds(5));
+  SimulateResponse(kConnectionId, /*content=*/"", net::HTTP_NOT_FOUND);
+  SimulateResponse(kConnectionId, /*content=*/"", net::HTTP_NOT_FOUND);
+  EXPECT_FALSE(stop_future.IsReady());
+
+  // Second failure. Poller should stop.
+  task_environment_.FastForwardBy(base::Seconds(5));
+  SimulateResponse(kConnectionId, /*content=*/"", net::HTTP_NOT_FOUND);
+  SimulateResponse(kConnectionId, /*content=*/"", net::HTTP_NOT_FOUND);
+
+  EXPECT_TRUE(stop_future.Get());  // server_unreachable is true.
+}
+
+}  // namespace
+}  // namespace ash::boca_receiver
diff --git a/chromeos/ash/components/boca/receiver/student_screen_presenter_impl.cc b/chromeos/ash/components/boca/receiver/student_screen_presenter_impl.cc
index 746dfd16..a4e1dd9 100644
--- a/chromeos/ash/components/boca/receiver/student_screen_presenter_impl.cc
+++ b/chromeos/ash/components/boca/receiver/student_screen_presenter_impl.cc
@@ -193,10 +193,8 @@
 void StudentScreenPresenterImpl::OnCheckConnectionResponse(
     std::optional<::boca::KioskReceiver> receiver) {
   if (!receiver.has_value() ||
-      receiver->state() != ::boca::ReceiverConnectionState::DISCONNECTED) {
-    if (!stop_success_callbacks_.empty() && !stopped_check_timer_.IsRunning()) {
-      NotifyStopSuccess(false);
-    }
+      (receiver->state() != ::boca::ReceiverConnectionState::DISCONNECTED &&
+       receiver->state() != ::boca::ReceiverConnectionState::ERROR)) {
     return;
   }
   if (!stop_success_callbacks_.empty()) {
@@ -210,21 +208,10 @@
 void StudentScreenPresenterImpl::OnStopResponse(
     std::optional<::boca::ReceiverConnectionState> connection_state) {
   stop_request_in_progress_ = false;
-  if (!connection_state.has_value()) {
-    NotifyStopSuccess(false);
-    return;
-  }
-  if (connection_state.value() ==
-      ::boca::ReceiverConnectionState::DISCONNECTED) {
-    NotifyStopSuccess(true);
+  NotifyStopSuccess(/*success=*/connection_state.has_value());
+  if (connection_state.has_value()) {
     Reset();
-    return;
   }
-  constexpr base::TimeDelta kStoppedCheckDelay = base::Seconds(5);
-  stopped_check_timer_.Start(
-      FROM_HERE, kStoppedCheckDelay,
-      base::BindOnce(&StudentScreenPresenterImpl::CheckConnection,
-                     weak_ptr_factory_.GetWeakPtr()));
 }
 
 void StudentScreenPresenterImpl::Reset() {
@@ -233,7 +220,6 @@
   student_id_.reset();
   disconnected_cb_.Reset();
   stop_request_in_progress_ = false;
-  stopped_check_timer_.Stop();
 }
 
 void StudentScreenPresenterImpl::NotifyStopSuccess(bool success) {
diff --git a/chromeos/ash/components/boca/receiver/student_screen_presenter_impl.h b/chromeos/ash/components/boca/receiver/student_screen_presenter_impl.h
index a280b8d..e57ad10 100644
--- a/chromeos/ash/components/boca/receiver/student_screen_presenter_impl.h
+++ b/chromeos/ash/components/boca/receiver/student_screen_presenter_impl.h
@@ -88,7 +88,6 @@
   base::OnceCallback<void(bool)> start_success_cb_;
   bool stop_request_in_progress_ = false;
   base::queue<base::OnceCallback<void(bool)>> stop_success_callbacks_;
-  base::OneShotTimer stopped_check_timer_;
 
   base::WeakPtrFactory<StudentScreenPresenterImpl> weak_ptr_factory_{this};
 };
diff --git a/chromeos/ash/components/boca/receiver/student_screen_presenter_impl_unittest.cc b/chromeos/ash/components/boca/receiver/student_screen_presenter_impl_unittest.cc
index b1b01d5..f6507095 100644
--- a/chromeos/ash/components/boca/receiver/student_screen_presenter_impl_unittest.cc
+++ b/chromeos/ash/components/boca/receiver/student_screen_presenter_impl_unittest.cc
@@ -40,6 +40,7 @@
 constexpr std::string_view kConnectionIdPair =
     R"({"connectionId": "connection-id"})";
 constexpr std::string_view kDisconnectedState = "DISCONNECTED";
+constexpr std::string_view kErrorState = "ERROR";
 constexpr std::string_view kStopRequestedState = "STOP_REQUESTED";
 
 constexpr char kBocaPresentStudentScreenResultUmaPath[] =
@@ -255,7 +256,11 @@
                                      /* failure */ 0, 1);
 }
 
-TEST_F(StudentScreenPresenterImplTest, CheckConnectionDisconnected) {
+class StudentScreenPresenterImplDisconnectTest
+    : public StudentScreenPresenterImplTest,
+      public testing::WithParamInterface<std::string_view> {};
+
+TEST_P(StudentScreenPresenterImplDisconnectTest, CheckConnection) {
   base::test::TestFuture<bool> start_future1;
   base::test::TestFuture<bool> start_future2;
   base::test::TestFuture<void> disconnected_future;
@@ -271,7 +276,7 @@
 
   url_loader_factory_.AddResponse(
       GetKioskReceiverUrl(kReceiverId, kConnectionId).spec(),
-      CheckConnectionStateJson(kDisconnectedState));
+      CheckConnectionStateJson(/*connection_state=*/GetParam()));
   presenter.CheckConnection();
   EXPECT_TRUE(disconnected_future.Wait());
 
@@ -283,6 +288,10 @@
   EXPECT_TRUE(start_future2.Get());
 }
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         StudentScreenPresenterImplDisconnectTest,
+                         testing::ValuesIn({kDisconnectedState, kErrorState}));
+
 TEST_F(StudentScreenPresenterImplTest, CheckConnectionNotDisconnected) {
   base::test::TestFuture<bool> start_future1;
   base::test::TestFuture<bool> start_future2;
@@ -401,64 +410,6 @@
   EXPECT_FALSE(start_future2.Get());
 }
 
-TEST_F(StudentScreenPresenterImplTest, StopDisconnectedAfterDelay) {
-  base::test::TestFuture<bool> start_future;
-  base::test::TestFuture<bool> stop_future;
-  base::test::TestFuture<void> disconnected_future;
-  StudentScreenPresenterImpl presenter(kSessionId, teacher_identity_,
-                                       kTeacherDeviceId,
-                                       url_loader_factory_.GetSafeWeakWrapper(),
-                                       identity_test_env_.identity_manager());
-  url_loader_factory_.AddResponse(GetStartReceiverUrl(kReceiverId).spec(),
-                                  kConnectionIdPair);
-  presenter.Start(kReceiverId, student_identity_, kStudentDeviceId,
-                  start_future.GetCallback(),
-                  disconnected_future.GetCallback());
-  EXPECT_TRUE(start_future.Get());
-
-  url_loader_factory_.AddResponse(
-      GetUpdateReceiverUrl(kReceiverId, kConnectionId).spec(),
-      UpdateConnectionStateJson(kStopRequestedState));
-  presenter.Stop(stop_future.GetCallback());
-  task_environment_.RunUntilIdle();
-
-  presenter.CheckConnection();
-  WaitAndRespond(GetKioskReceiverUrl(kReceiverId, kConnectionId),
-                 CheckConnectionStateJson(kStopRequestedState));
-
-  task_environment_.FastForwardBy(base::Seconds(5));
-  WaitAndRespond(GetKioskReceiverUrl(kReceiverId, kConnectionId),
-                 CheckConnectionStateJson(kDisconnectedState));
-  EXPECT_TRUE(stop_future.Get());
-  EXPECT_FALSE(disconnected_future.IsReady());
-}
-
-TEST_F(StudentScreenPresenterImplTest, StopStillConnectedAfterDelay) {
-  base::test::TestFuture<bool> start_future;
-  base::test::TestFuture<bool> stop_future;
-  StudentScreenPresenterImpl presenter(kSessionId, teacher_identity_,
-                                       kTeacherDeviceId,
-                                       url_loader_factory_.GetSafeWeakWrapper(),
-                                       identity_test_env_.identity_manager());
-  url_loader_factory_.AddResponse(GetStartReceiverUrl(kReceiverId).spec(),
-                                  kConnectionIdPair);
-  presenter.Start(kReceiverId, student_identity_, kStudentDeviceId,
-                  start_future.GetCallback(), base::DoNothing());
-  EXPECT_TRUE(start_future.Get());
-
-  url_loader_factory_.AddResponse(
-      GetUpdateReceiverUrl(kReceiverId, kConnectionId).spec(),
-      UpdateConnectionStateJson(kStopRequestedState));
-  presenter.Stop(stop_future.GetCallback());
-  task_environment_.RunUntilIdle();
-
-  url_loader_factory_.AddResponse(
-      GetKioskReceiverUrl(kReceiverId, kConnectionId).spec(),
-      CheckConnectionStateJson(kStopRequestedState));
-  task_environment_.FastForwardBy(base::Seconds(5));
-  EXPECT_FALSE(stop_future.Get());
-}
-
 TEST_F(StudentScreenPresenterImplTest, CheckConnectionBeforeStopRequest) {
   base::test::TestFuture<bool> start_future;
   base::test::TestFuture<bool> stop_future;
@@ -523,11 +474,9 @@
   EXPECT_FALSE(disconnected_future.IsReady());
 }
 
-TEST_F(StudentScreenPresenterImplTest,
-       CheckConnectionDisconnectedAfterStopResponse) {
+TEST_F(StudentScreenPresenterImplTest, CheckConnectionAfterStopResponse) {
   base::test::TestFuture<bool> start_future;
   base::test::TestFuture<bool> stop_future;
-  base::test::TestFuture<void> disconnected_future;
   StudentScreenPresenterImpl presenter(kSessionId, teacher_identity_,
                                        kTeacherDeviceId,
                                        url_loader_factory_.GetSafeWeakWrapper(),
@@ -535,58 +484,19 @@
   url_loader_factory_.AddResponse(GetStartReceiverUrl(kReceiverId).spec(),
                                   kConnectionIdPair);
   presenter.Start(kReceiverId, student_identity_, kStudentDeviceId,
-                  start_future.GetCallback(),
-                  disconnected_future.GetCallback());
+                  start_future.GetCallback(), base::DoNothing());
   EXPECT_TRUE(start_future.Get());
 
   url_loader_factory_.AddResponse(
       GetUpdateReceiverUrl(kReceiverId, kConnectionId).spec(),
       UpdateConnectionStateJson(kStopRequestedState));
   presenter.Stop(stop_future.GetCallback());
-  task_environment_.RunUntilIdle();
-
-  url_loader_factory_.AddResponse(
-      GetKioskReceiverUrl(kReceiverId, kConnectionId).spec(),
-      CheckConnectionStateJson(kDisconnectedState));
-  presenter.CheckConnection();
   EXPECT_TRUE(stop_future.Get());
 
-  task_environment_.FastForwardBy(base::Seconds(5));
+  presenter.CheckConnection();
   EXPECT_EQ(url_loader_factory_.NumPending(), 0);
 }
 
-TEST_F(StudentScreenPresenterImplTest, CheckConnectionFailedAfterStopResponse) {
-  base::test::TestFuture<bool> start_future;
-  base::test::TestFuture<bool> stop_future;
-  base::test::TestFuture<void> disconnected_future;
-  StudentScreenPresenterImpl presenter(kSessionId, teacher_identity_,
-                                       kTeacherDeviceId,
-                                       url_loader_factory_.GetSafeWeakWrapper(),
-                                       identity_test_env_.identity_manager());
-  url_loader_factory_.AddResponse(GetStartReceiverUrl(kReceiverId).spec(),
-                                  kConnectionIdPair);
-  presenter.Start(kReceiverId, student_identity_, kStudentDeviceId,
-                  start_future.GetCallback(),
-                  disconnected_future.GetCallback());
-  EXPECT_TRUE(start_future.Get());
-
-  url_loader_factory_.AddResponse(
-      GetUpdateReceiverUrl(kReceiverId, kConnectionId).spec(),
-      UpdateConnectionStateJson(kStopRequestedState));
-  presenter.Stop(stop_future.GetCallback());
-  task_environment_.RunUntilIdle();
-
-  presenter.CheckConnection();
-  WaitAndRespond(GetKioskReceiverUrl(kReceiverId, kConnectionId), "",
-                 net::HTTP_INTERNAL_SERVER_ERROR);
-
-  task_environment_.FastForwardBy(base::Seconds(5));
-  WaitAndRespond(GetKioskReceiverUrl(kReceiverId, kConnectionId),
-                 CheckConnectionStateJson(kDisconnectedState));
-  EXPECT_TRUE(stop_future.Get());
-  EXPECT_FALSE(disconnected_future.IsReady());
-}
-
 TEST_F(StudentScreenPresenterImplTest, StopWithoutStart) {
   base::test::TestFuture<bool> stop_future;
   StudentScreenPresenterImpl presenter(kSessionId, teacher_identity_,
diff --git a/chromeos/ash/components/geolocation/BUILD.gn b/chromeos/ash/components/geolocation/BUILD.gn
index c2062f1..b8e9a349 100644
--- a/chromeos/ash/components/geolocation/BUILD.gn
+++ b/chromeos/ash/components/geolocation/BUILD.gn
@@ -16,6 +16,8 @@
     "//services/network/public/cpp",
   ]
   sources = [
+    "cached_location_provider.cc",
+    "cached_location_provider.h",
     "geoposition.cc",
     "geoposition.h",
     "live_location_provider.cc",
@@ -49,6 +51,7 @@
     "//testing/gtest",
   ]
   sources = [
+    "cached_location_provider_unittest.cc",
     "location_fetcher_unittest.cc",
     "system_location_provider_unittest.cc",
     "test_utils.cc",
diff --git a/chromeos/ash/components/geolocation/cached_location_provider.cc b/chromeos/ash/components/geolocation/cached_location_provider.cc
new file mode 100644
index 0000000..3f77239
--- /dev/null
+++ b/chromeos/ash/components/geolocation/cached_location_provider.cc
@@ -0,0 +1,163 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/ash/components/geolocation/cached_location_provider.h"
+
+#include <algorithm>
+#include <memory>
+#include <optional>
+
+#include "base/functional/bind.h"
+#include "base/time/time.h"
+#include "chromeos/ash/components/geolocation/location_fetcher.h"
+#include "chromeos/ash/components/network/network_util.h"
+
+namespace ash {
+
+// Evict the cache if the set of surrounding unique Wi-Fi Access Points or
+// Cellular towers has changed. A change in signal strength is ignored.
+class ScanEqualityEviction : public CachedLocationProvider::CacheEviction {
+ public:
+  bool IsSignificantDisplacementIndicated(
+      const CachedLocationProvider::GeopositionCache::Context& context_a,
+      const CachedLocationProvider::GeopositionCache::Context& context_b)
+      const override {
+    bool is_wifi_scan_same =
+        std::equal(context_a.wifi_context.begin(), context_a.wifi_context.end(),
+                   context_b.wifi_context.begin(), context_b.wifi_context.end(),
+                   [](const WifiAccessPoint& a, const WifiAccessPoint& b) {
+                     return a.mac_address == b.mac_address;
+                   });
+    bool is_cellular_scan_same = std::equal(
+        context_a.cell_tower_context.begin(),
+        context_a.cell_tower_context.end(),
+        context_b.cell_tower_context.begin(),
+        context_b.cell_tower_context.end(),
+        [](const CellTower& a, const CellTower& b) { return a.ci == b.ci; });
+
+    return !is_wifi_scan_same || !is_cellular_scan_same;
+  }
+};
+
+CachedLocationProvider::GeopositionCache::Context::Context() = default;
+
+CachedLocationProvider::GeopositionCache::Context::Context(Context&& other) =
+    default;
+
+CachedLocationProvider::GeopositionCache::Context&
+CachedLocationProvider::GeopositionCache::Context::operator=(Context&& other) =
+    default;
+
+CachedLocationProvider::GeopositionCache::Context::~Context() = default;
+
+CachedLocationProvider::GeopositionCache::GeopositionCache() = default;
+
+CachedLocationProvider::GeopositionCache::GeopositionCache(
+    Geoposition position,
+    base::TimeTicks fetch_time,
+    std::optional<Context> context)
+    : position(position), fetch_time(fetch_time), context(std::move(context)) {}
+
+CachedLocationProvider::GeopositionCache::GeopositionCache(
+    CachedLocationProvider::GeopositionCache&&) = default;
+
+CachedLocationProvider::GeopositionCache&
+CachedLocationProvider::GeopositionCache::operator=(
+    CachedLocationProvider::GeopositionCache&&) = default;
+
+CachedLocationProvider::GeopositionCache::~GeopositionCache() = default;
+
+CachedLocationProvider::CachedLocationProvider(
+    std::unique_ptr<LocationFetcher> location_fetcher)
+    : LocationProvider(std::move(location_fetcher)),
+      cache_eviction_method_(std::make_unique<ScanEqualityEviction>()) {}
+CachedLocationProvider::~CachedLocationProvider() = default;
+
+void CachedLocationProvider::RequestLocation(base::TimeDelta timeout,
+                                             bool send_wifi_access_points,
+                                             bool send_cell_towers,
+                                             ResponseCallback callback) {
+  std::optional<GeopositionCache>* cache_to_use;
+  std::optional<GeopositionCache::Context> context_to_use;
+  std::optional<CacheEviction*> eviction_to_use;
+
+  // Determine if this is a Precise or Coarse request.
+  if (send_wifi_access_points || send_cell_towers) {
+    cache_to_use = &precise_location_cache_;
+    context_to_use.emplace(GetPreciseLocationContext());
+    eviction_to_use = cache_eviction_method_.get();
+  } else {
+    cache_to_use = &coarse_location_cache_;
+    context_to_use = std::nullopt;
+    eviction_to_use = std::nullopt;
+  }
+
+  // Return cached position whenever possible.
+  if (IsCacheUsable(*cache_to_use, context_to_use, eviction_to_use)) {
+    std::move(callback).Run(cache_to_use->value().position, false,
+                            base::Seconds(0));
+    return;
+  }
+
+  // Initiate a new network request. We bind `cache_to_use` (pointer to member)
+  // so the correct cache is updated when the request completes.
+  location_fetcher_->RequestGeolocation(
+      timeout, send_wifi_access_points, send_cell_towers,
+      base::BindOnce(&CachedLocationProvider::OnLocationResolved,
+                     weak_ptr_factory_.GetWeakPtr(), cache_to_use,
+                     std::move(context_to_use), std::move(callback)));
+}
+
+bool CachedLocationProvider::IsCacheUsable(
+    const std::optional<GeopositionCache>& location_cache,
+    const std::optional<GeopositionCache::Context>& new_context,
+    const std::optional<CacheEviction*> eviction_strategy) {
+  if (!location_cache) {
+    return false;
+  }
+
+  // 1. Time-based validity:
+  // If the cache is fresh (fetched within the rate limit window), reuse it
+  // immediately regardless of potential movement.
+  if (base::TimeTicks::Now() - location_cache->fetch_time <= rate_limit_) {
+    return true;
+  }
+
+  // 2. Spatial validity (Precise Location only):
+  // If the cache is stale by time, use selected `CacheEviction` heuristics.
+  if (eviction_strategy) {
+    CHECK(new_context);
+    return !eviction_strategy.value()->IsSignificantDisplacementIndicated(
+        location_cache->context.value(), new_context.value());
+  }
+
+  return false;
+}
+
+void CachedLocationProvider::OnLocationResolved(
+    std::optional<GeopositionCache>* cache_to_use,
+    std::optional<GeopositionCache::Context> request_context,
+    ResponseCallback callback,
+    const Geoposition& position,
+    bool server_error,
+    base::TimeDelta elapsed) {
+  // If the request succeeded, update the specific cache member.
+  if (!server_error && position.Valid()) {
+    cache_to_use->emplace(GeopositionCache(position, base::TimeTicks::Now(),
+                                           std::move(request_context)));
+  }
+
+  std::move(callback).Run(position, server_error, elapsed);
+}
+
+CachedLocationProvider::GeopositionCache::Context
+CachedLocationProvider::GetPreciseLocationContext() {
+  GeopositionCache::Context current_context;
+  location_fetcher_->GetNetworkInformation(&current_context.wifi_context,
+                                           &current_context.cell_tower_context);
+
+  return current_context;
+}
+
+}  // namespace ash
diff --git a/chromeos/ash/components/geolocation/cached_location_provider.h b/chromeos/ash/components/geolocation/cached_location_provider.h
new file mode 100644
index 0000000..5950f70
--- /dev/null
+++ b/chromeos/ash/components/geolocation/cached_location_provider.h
@@ -0,0 +1,164 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_ASH_COMPONENTS_GEOLOCATION_CACHED_LOCATION_PROVIDER_H_
+#define CHROMEOS_ASH_COMPONENTS_GEOLOCATION_CACHED_LOCATION_PROVIDER_H_
+
+#include <optional>
+
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "chromeos/ash/components/geolocation/geoposition.h"
+#include "chromeos/ash/components/geolocation/location_provider.h"
+#include "chromeos/ash/components/network/network_util.h"
+
+namespace ash {
+
+// Manages caching for resolved geolocation data to optimize performance and
+// control remote API usage.
+//
+// The class maintains separate caches for coarse (IP-based) and precise
+// (wireless signal-based) locations to adhere to strict privacy requirements.
+//
+// 1. Rate Limiting: Implements a throttling mechanism to govern the frequency
+// of outbound requests and prevent resource depletion of the Geolocation API
+// Web Service.
+// 2. Cache Validity: Requests are not always served with real-time data. For
+// precise location calls, cached data is returned if the underlying wireless
+// signals have not changed significantly, based on defined displacement
+// criteria.
+// TODO(crbug.com/463591748): Refactor to avoid conditional handling of
+// the coarse/precise flows.
+class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GEOLOCATION)
+    CachedLocationProvider : public LocationProvider {
+ public:
+  explicit CachedLocationProvider(
+      std::unique_ptr<LocationFetcher> location_fetcher);
+  ~CachedLocationProvider() override;
+
+  // LocationProvider:
+  void RequestLocation(base::TimeDelta timeout,
+                       bool use_wifi,
+                       bool use_cell_towers,
+                       ResponseCallback callback) override;
+
+  base::TimeDelta GetRateLimitForTesting() { return rate_limit_; }
+
+ private:
+  friend class ScanEqualityEviction;
+
+  // Represents a single location cache entry. It combines the computed position
+  // with the context used to generate it.
+  struct GeopositionCache {
+    struct Context {
+      // The list of observed Wi-Fi access points used for the geocoding
+      // request.
+      WifiAccessPointVector wifi_context;
+      // The list of observed cell towers used for the geocoding request.
+      CellTowerVector cell_tower_context;
+
+      Context();
+
+      // Move-only.
+      Context(const Context&) = delete;
+      Context& operator=(const Context&) = delete;
+      Context(Context&&);
+      Context& operator=(Context&&);
+
+      ~Context();
+    };
+
+    // The actual latitude, longitude, and accuracy of the position.
+    Geoposition position;
+
+    // The time the Geoposition was successfully fetched from the server, it's
+    // the time when the location was received, not when it was resolved on
+    // remote end. Used for determining cache freshness/expiration.
+    base::TimeTicks fetch_time;
+
+    // The Wi-Fi and cell tower information used as input to generate the
+    // `position`. This is optional because coarse location requests don't use
+    // this field.
+    std::optional<Context> context;
+
+    GeopositionCache();
+    GeopositionCache(Geoposition, base::TimeTicks, std::optional<Context>);
+
+    // Move-only.
+    GeopositionCache(const GeopositionCache&) = delete;
+    GeopositionCache& operator=(const GeopositionCache&) = delete;
+    GeopositionCache(GeopositionCache&&);
+    GeopositionCache& operator=(GeopositionCache&&);
+
+    ~GeopositionCache();
+  };
+
+  class CacheEviction {
+   public:
+    virtual ~CacheEviction() = default;
+
+    // Determines if the significant physical displacement is implied by the
+    // difference of the two context points. This would trigger a cache eviction
+    // action.
+    //
+    // The definition of "significant" is implementation-dependent, but all
+    // concrete implementations must provide a policy relevant for the ChromeOS
+    // system services (all `SystemLocationProvider` clients).
+    virtual bool IsSignificantDisplacementIndicated(
+        const GeopositionCache::Context& context_a,
+        const GeopositionCache::Context& context_b) const = 0;
+  };
+
+  // Checks if the specified `location_cache` is valid and fresh enough to serve
+  // the current request.
+  //
+  // Returns true if the cache is within the time limit and, for precise
+  // requests, if the displacement since the last fetch is not expected to be
+  // significant according to `eviction_strategy`.
+  //
+  // `new_context` and `eviction_strategy` must be null for coarse location
+  // checks.
+  bool IsCacheUsable(
+      const std::optional<GeopositionCache>& location_cache,
+      const std::optional<GeopositionCache::Context>& new_context,
+      const std::optional<CacheEviction*> eviction_strategy);
+
+  // Callback for the remote Geolocation API request.
+  //
+  // Upon a successful resolution, updates the specific cache pointed to by
+  // `cache_to_use` (either the coarse or precise member cache) and passes the
+  // result to the original `callback`.
+  void OnLocationResolved(
+      std::optional<GeopositionCache>* cache_to_use,
+      std::optional<GeopositionCache::Context> request_context,
+      ResponseCallback callback,
+      const Geoposition& position,
+      bool server_error,
+      base::TimeDelta elapsed);
+
+  // Retrieves the current Wi-Fi and cell tower data for precise location.
+  GeopositionCache::Context GetPreciseLocationContext();
+
+  // The minimum time that must elapse between successful outbound requests.
+  // Applies to both Precise and Coarse requests.
+  // If a request is made before this duration has passed since the last
+  // successful fetch, the cached position will be returned instead of
+  // initiating a new network request.
+  base::TimeDelta rate_limit_ = base::Hours(1);
+
+  // Stores the most recently resolved, respectively coarse and precise,
+  // locations. These caches are separate for privacy reasons.
+  std::optional<GeopositionCache> coarse_location_cache_;
+  std::optional<GeopositionCache> precise_location_cache_;
+
+  // The strategy object used to determine if two contexts represent a
+  // significant displacement to clear the cache.
+  std::unique_ptr<CacheEviction> cache_eviction_method_;
+
+  base::WeakPtrFactory<CachedLocationProvider> weak_ptr_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // CHROMEOS_ASH_COMPONENTS_GEOLOCATION_CACHED_LOCATION_PROVIDER_H_
diff --git a/chromeos/ash/components/geolocation/cached_location_provider_unittest.cc b/chromeos/ash/components/geolocation/cached_location_provider_unittest.cc
new file mode 100644
index 0000000..e3245c6f
--- /dev/null
+++ b/chromeos/ash/components/geolocation/cached_location_provider_unittest.cc
@@ -0,0 +1,316 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/ash/components/geolocation/cached_location_provider.h"
+
+#include "base/memory/scoped_refptr.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "base/time/time.h"
+#include "chromeos/ash/components/geolocation/geoposition.h"
+#include "chromeos/ash/components/geolocation/location_fetcher.h"
+#include "chromeos/ash/components/geolocation/test_utils.h"
+#include "chromeos/ash/components/network/network_util.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+namespace {
+
+constexpr base::TimeDelta kRequestTimeout = base::Milliseconds(100);
+
+WifiAccessPoint GetTestWifiAP() {
+  WifiAccessPoint wifi_ap;
+  wifi_ap.ssid = "ssid_0";
+  wifi_ap.mac_address = "01:00:00:00:00:00";
+  wifi_ap.timestamp = base::Time();
+  wifi_ap.signal_strength = 10;
+  wifi_ap.signal_to_noise = 0;
+  wifi_ap.channel = 1;
+
+  return wifi_ap;
+}
+
+CellTower GetTestCellTower() {
+  CellTower cell_tower;
+  cell_tower.mcc = base::NumberToString(100);
+  cell_tower.mnc = base::NumberToString(101);
+  cell_tower.lac = base::NumberToString(3);
+  cell_tower.ci = base::NumberToString(1);
+  cell_tower.timestamp = base::Time();
+
+  return cell_tower;
+}
+
+}  // namespace
+
+namespace utils = geolocation::test_utils;
+
+class CachedLocationProviderTestBase
+    : public testing::TestWithParam<std::tuple<bool, bool>> {
+ public:
+  CachedLocationProviderTestBase() : interceptor(&url_factory_) {
+    use_wifi_scan = std::get<0>(GetParam());
+    use_cellular_scan = std::get<1>(GetParam());
+  }
+
+  void SetUp() override {
+    network_delegate.AddWifiAP(GetTestWifiAP());
+    network_delegate.AddCellTower(GetTestCellTower());
+
+    url_factory_.SetInterceptor(
+        base::BindRepeating(&utils::GeolocationApiInterceptor::Intercept,
+                            base::Unretained(&interceptor)));
+    cached_location_provider = std::make_unique<CachedLocationProvider>(
+        std::make_unique<LocationFetcher>(
+            base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+                &url_factory_),
+            GURL(utils::kTestGeolocationProviderUrl), &network_delegate));
+  }
+
+  void ConfigureLocationResponse(const Geoposition& position) {
+    auto access_points = std::make_unique<WifiAccessPointVector>();
+    auto cell_towers = std::make_unique<CellTowerVector>();
+    network_delegate.GetNetworkInformation(access_points.get(),
+                                           cell_towers.get());
+
+    if (!use_wifi_scan) {
+      access_points.reset();
+    }
+
+    if (!use_cellular_scan) {
+      cell_towers.reset();
+    }
+
+    interceptor.RegisterResponseForQuery(
+        GetRequestBodyFor(std::move(access_points), std::move(cell_towers)),
+        position);
+  }
+
+  base::TimeDelta GetRateLimit() {
+    return cached_location_provider->GetRateLimitForTesting();
+  }
+
+  bool IsForPreciseResolution(bool use_wifi_signals,
+                              bool use_cellular_signals) {
+    return use_wifi_signals || use_cellular_signals;
+  }
+
+ protected:
+  bool use_wifi_scan;
+  bool use_cellular_scan;
+
+  utils::GeolocationApiInterceptor interceptor;
+  std::unique_ptr<ash::CachedLocationProvider> cached_location_provider;
+  utils::FakeNetworkDelegate network_delegate;
+  network::TestURLLoaderFactory url_factory_;
+
+ private:
+  std::string GetRequestBodyFor(
+      std::unique_ptr<WifiAccessPointVector> access_points,
+      std::unique_ptr<CellTowerVector> cell_towers) {
+    // `LocationFetcher::RequestGeolocation` drops empty scans.
+    if (!access_points || access_points->empty()) {
+      access_points.reset(nullptr);
+    }
+    if (!cell_towers || cell_towers->empty()) {
+      cell_towers.reset(nullptr);
+    }
+
+    auto request = SimpleGeolocationRequest(
+        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+            &url_factory_),
+        GURL(utils::kTestGeolocationProviderUrl), base::TimeDelta(),
+        std::move(access_points), std::move(cell_towers));
+    return request.FormatRequestBodyForTesting();
+  }
+};
+
+class CachedLocationProviderTest : public CachedLocationProviderTestBase {
+ private:
+  base::test::SingleThreadTaskEnvironment task_environment;
+};
+
+TEST_P(CachedLocationProviderTest, FirstRequestFetchesLocation) {
+  base::test::TestFuture<const Geoposition&, bool, base::TimeDelta> future;
+  cached_location_provider->RequestLocation(
+      kRequestTimeout, use_wifi_scan, use_cellular_scan, future.GetCallback());
+  // Wait for the response.
+  EXPECT_TRUE(future.Wait());
+
+  // Check that the API has been called.
+  EXPECT_EQ(1U, interceptor.attempts());
+}
+
+TEST_P(CachedLocationProviderTest, SequentialRequestReturnsCache) {
+  Geoposition first_position;
+  {
+    base::test::TestFuture<const Geoposition&, bool, base::TimeDelta> future;
+    cached_location_provider->RequestLocation(kRequestTimeout, use_wifi_scan,
+                                              use_cellular_scan,
+                                              future.GetCallback());
+    first_position = future.Get<0>();
+    EXPECT_EQ(1U, interceptor.attempts());
+  }
+
+  // Second request.
+  {
+    base::test::TestFuture<const Geoposition&, bool, base::TimeDelta> future;
+    cached_location_provider->RequestLocation(kRequestTimeout, use_wifi_scan,
+                                              use_cellular_scan,
+                                              future.GetCallback());
+    auto second_position = future.Get<0>();
+
+    // Check that new API call was not and the position is the same.
+    EXPECT_EQ(1U, interceptor.attempts());
+    EXPECT_EQ(first_position, second_position);
+  }
+}
+
+TEST_P(CachedLocationProviderTest, CoarseAndPreciseCachesAreSeparate) {
+  Geoposition first_position;
+  {
+    base::test::TestFuture<const Geoposition&, bool, base::TimeDelta> future;
+    cached_location_provider->RequestLocation(kRequestTimeout, use_wifi_scan,
+                                              use_cellular_scan,
+                                              future.GetCallback());
+    first_position = future.Get<0>();
+    EXPECT_EQ(1U, interceptor.attempts());
+  }
+
+  // Arguments to trigger an opposite granularity requests.
+  bool opposite_use_wifi_scan;
+  bool opposite_use_cellular_scan;
+  if (IsForPreciseResolution(use_wifi_scan, use_cellular_scan)) {
+    opposite_use_wifi_scan = false;
+    opposite_use_cellular_scan = false;
+  } else {
+    opposite_use_wifi_scan = true;
+    opposite_use_cellular_scan = true;
+  }
+
+  // Request location for the opposite granularity.
+  Geoposition second_position;
+  {
+    base::test::TestFuture<const Geoposition&, bool, base::TimeDelta> future;
+    cached_location_provider->RequestLocation(
+        kRequestTimeout, opposite_use_wifi_scan, opposite_use_cellular_scan,
+        future.GetCallback());
+    second_position = future.Get<0>();
+    EXPECT_EQ(2U, interceptor.attempts());
+  }
+
+  // Check that resolved positions are different.
+  EXPECT_NE(first_position, second_position);
+
+  // Check that subsequent requests for both granularity will return respective
+  // cached values:
+  {
+    base::test::TestFuture<const Geoposition&, bool, base::TimeDelta> future;
+    cached_location_provider->RequestLocation(kRequestTimeout, use_wifi_scan,
+                                              use_cellular_scan,
+                                              future.GetCallback());
+    auto position = future.Get<0>();
+    EXPECT_EQ(2U, interceptor.attempts());
+    EXPECT_EQ(first_position, position);
+  }
+  {
+    base::test::TestFuture<const Geoposition&, bool, base::TimeDelta> future;
+    cached_location_provider->RequestLocation(
+        kRequestTimeout, opposite_use_wifi_scan, opposite_use_cellular_scan,
+        future.GetCallback());
+    auto position = future.Get<0>();
+    EXPECT_EQ(2U, interceptor.attempts());
+    EXPECT_EQ(second_position, position);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    CachedLocationProviderTest,
+    testing::Combine(testing::Bool(), testing::Bool()),
+    [](const testing::TestParamInfo<std::tuple<bool, bool>>& info) {
+      return base::StringPrintf("wifi%d_cellular%d", std::get<0>(info.param),
+                                std::get<1>(info.param));
+    });
+
+class CachedLocationProviderMockTimeTest
+    : public CachedLocationProviderTestBase {
+ protected:
+  base::test::SingleThreadTaskEnvironment task_environment{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+};
+
+TEST_P(CachedLocationProviderMockTimeTest, CheckRateLimit) {
+  Geoposition first_position;
+
+  // First request always fetches.
+  {
+    base::test::TestFuture<const Geoposition&, bool, base::TimeDelta> future;
+    cached_location_provider->RequestLocation(kRequestTimeout, use_wifi_scan,
+                                              use_cellular_scan,
+                                              future.GetCallback());
+    task_environment.FastForwardBy(kRequestTimeout);
+    first_position = future.Get<0>();
+    EXPECT_EQ(1U, interceptor.attempts());
+  }
+
+  // Check that request at the end of the rate limit period will return cache.
+  task_environment.FastForwardBy(GetRateLimit() - kRequestTimeout);
+  {
+    base::test::TestFuture<const Geoposition&, bool, base::TimeDelta> future;
+    cached_location_provider->RequestLocation(kRequestTimeout, use_wifi_scan,
+                                              use_cellular_scan,
+                                              future.GetCallback());
+    task_environment.FastForwardBy(kRequestTimeout);
+    auto position = future.Get<0>();
+    EXPECT_EQ(1U, interceptor.attempts());
+    EXPECT_EQ(first_position, position);
+  }
+
+  // Escape from the rate limited interval.
+  task_environment.FastForwardBy(base::Milliseconds(1));
+
+  // Clear network signals to avoid cache-hit.
+  if (IsForPreciseResolution(use_wifi_scan, use_cellular_scan)) {
+    network_delegate.RemoveWifiAP(GetTestWifiAP());
+    network_delegate.RemoveCellTower(GetTestCellTower());
+  }
+
+  // Configure Remote API to return different location:
+  auto new_expected_position = Geoposition();
+  new_expected_position.latitude = 10;
+  new_expected_position.longitude = 10;
+  new_expected_position.accuracy = 100;
+  new_expected_position.status = Geoposition::STATUS_OK;
+  new_expected_position.timestamp = base::Time::Now();
+  ConfigureLocationResponse(new_expected_position);
+
+  // Check that the request fetches fresh location.
+  {
+    base::test::TestFuture<const Geoposition&, bool, base::TimeDelta> future;
+    cached_location_provider->RequestLocation(kRequestTimeout, use_wifi_scan,
+                                              use_cellular_scan,
+                                              future.GetCallback());
+    task_environment.FastForwardBy(kRequestTimeout);
+    auto position = future.Get<0>();
+    EXPECT_EQ(2U, interceptor.attempts());
+    EXPECT_EQ(new_expected_position, position);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    CachedLocationProviderMockTimeTest,
+    testing::Combine(testing::Bool(), testing::Bool()),
+    [](const testing::TestParamInfo<std::tuple<bool, bool>>& info) {
+      return base::StringPrintf("wifi%d_cellular%d", std::get<0>(info.param),
+                                std::get<1>(info.param));
+    });
+
+}  // namespace ash
diff --git a/chromeos/ash/components/geolocation/geoposition.h b/chromeos/ash/components/geolocation/geoposition.h
index 5b813ed7..d266699 100644
--- a/chromeos/ash/components/geolocation/geoposition.h
+++ b/chromeos/ash/components/geolocation/geoposition.h
@@ -59,6 +59,8 @@
 
   // See enum above.
   Status status;
+
+  bool operator==(const Geoposition&) const = default;
 };
 
 }  // namespace ash
diff --git a/chromeos/ash/components/geolocation/location_fetcher_unittest.cc b/chromeos/ash/components/geolocation/location_fetcher_unittest.cc
index 53f8e74..af304097 100644
--- a/chromeos/ash/components/geolocation/location_fetcher_unittest.cc
+++ b/chromeos/ash/components/geolocation/location_fetcher_unittest.cc
@@ -30,6 +30,29 @@
 constexpr base::TimeDelta kRequestTimeout = base::Seconds(1);
 constexpr base::TimeDelta kRetryDelay = base::Milliseconds(10);
 
+WifiAccessPoint GetTestWifiAp() {
+  WifiAccessPoint wifi_ap;
+  wifi_ap.ssid = "ssid_0";
+  wifi_ap.mac_address = "01:00:00:00:00:00";
+  wifi_ap.timestamp = base::Time();
+  wifi_ap.signal_strength = 10;
+  wifi_ap.signal_to_noise = 0;
+  wifi_ap.channel = 1;
+
+  return wifi_ap;
+}
+
+CellTower GetTestCellTower() {
+  CellTower cell_tower;
+  cell_tower.mcc = base::NumberToString(100);
+  cell_tower.mnc = base::NumberToString(101);
+  cell_tower.lac = base::NumberToString(3);
+  cell_tower.ci = base::NumberToString(1);
+  cell_tower.timestamp = base::Time();
+
+  return cell_tower;
+}
+
 }  // namespace
 
 class LocationFetcherTest : public testing::Test {
@@ -67,8 +90,8 @@
         retry_interval);
   }
 
-  void AddWifiAP() { network_delegate.AddWifiAP(); }
-  void AddCellTower() { network_delegate.AddCellTower(); }
+  void AddWifiAP() { network_delegate.AddWifiAP(GetTestWifiAp()); }
+  void AddCellTower() { network_delegate.AddCellTower(GetTestCellTower()); }
 
  private:
   base::test::SingleThreadTaskEnvironment task_environment_;
diff --git a/chromeos/ash/components/geolocation/location_provider.h b/chromeos/ash/components/geolocation/location_provider.h
index d6269999..437d37b 100644
--- a/chromeos/ash/components/geolocation/location_provider.h
+++ b/chromeos/ash/components/geolocation/location_provider.h
@@ -25,6 +25,8 @@
 // Concrete strategy implementations include:
 // * LiveLocationProvider: Delivers real-time (live) location data for every
 // request.
+// * CachedLocationProvider: Serves cached location data when validity criteria
+// are met, to prevent excessive Geolocation API usage.
 class LocationProvider {
  public:
   // Called when a new geolocation information is available.
diff --git a/chromeos/ash/components/geolocation/test_utils.cc b/chromeos/ash/components/geolocation/test_utils.cc
index 782e0ff..0c2ce31 100644
--- a/chromeos/ash/components/geolocation/test_utils.cc
+++ b/chromeos/ash/components/geolocation/test_utils.cc
@@ -8,6 +8,7 @@
 #include "base/run_loop.h"
 #include "chromeos/ash/components/geolocation/location_fetcher.h"
 #include "chromeos/ash/components/geolocation/system_location_provider.h"
+#include "chromeos/ash/components/network/network_util.h"
 #include "net/http/http_status_code.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/test/test_url_loader_factory.h"
@@ -131,24 +132,18 @@
   return true;
 }
 
-void FakeNetworkDelegate::AddWifiAP() {
-  WifiAccessPoint wifi_ap;
-  wifi_ap.channel = 1;
-  wifi_ap.mac_address = "01:00:00:00:00:00";
-  wifi_ap.signal_strength = 10;
-  wifi_ap.signal_to_noise = 0;
-
+void FakeNetworkDelegate::AddWifiAP(const WifiAccessPoint wifi_ap) {
   wifi_aps_.emplace_back(std::move(wifi_ap));
 }
+bool FakeNetworkDelegate::RemoveWifiAP(const WifiAccessPoint wifi_ap) {
+  return std::erase(wifi_aps_, wifi_ap);
+}
 
-void FakeNetworkDelegate::AddCellTower() {
-  CellTower cell_tower;
-  cell_tower.ci = base::NumberToString(1);
-  cell_tower.lac = base::NumberToString(3);
-  cell_tower.mcc = base::NumberToString(100);
-  cell_tower.mnc = base::NumberToString(101);
-
+void FakeNetworkDelegate::AddCellTower(const CellTower cell_tower) {
   cell_towers_.emplace_back(std::move(cell_tower));
 }
+bool FakeNetworkDelegate::RemoveCellTower(const CellTower cell_tower) {
+  return std::erase(cell_towers_, cell_tower);
+}
 
 }  // namespace ash::geolocation::test_utils
diff --git a/chromeos/ash/components/geolocation/test_utils.h b/chromeos/ash/components/geolocation/test_utils.h
index b6a53b2..3c91f95 100644
--- a/chromeos/ash/components/geolocation/test_utils.h
+++ b/chromeos/ash/components/geolocation/test_utils.h
@@ -216,10 +216,13 @@
   bool GetWifiAccessPoints(WifiAccessPointVector* access_points,
                            int64_t* age_ms) override;
 
-  // Add single Wifi scan.
-  void AddWifiAP();
-  // Add single Cell Tower scan.
-  void AddCellTower();
+  // Add/remove a `WifiAccessPoint` to the scan data.
+  void AddWifiAP(const WifiAccessPoint wifi_ap);
+  bool RemoveWifiAP(const WifiAccessPoint wifi_ap);
+
+  // Add/remove a `CellTower` to the scan data.
+  void AddCellTower(const CellTower cell_tower);
+  bool RemoveCellTower(const CellTower cell_tower);
 
  private:
   WifiAccessPointVector wifi_aps_;
diff --git a/chromeos/ash/components/network/network_util.h b/chromeos/ash/components/network/network_util.h
index 708e11e2..4f503cb 100644
--- a/chromeos/ash/components/network/network_util.h
+++ b/chromeos/ash/components/network/network_util.h
@@ -27,29 +27,35 @@
 
 // Struct for passing wifi access point data.
 struct COMPONENT_EXPORT(CHROMEOS_NETWORK) WifiAccessPoint {
-  WifiAccessPoint();
-  WifiAccessPoint(const WifiAccessPoint& other);
-  ~WifiAccessPoint();
   std::string ssid;  // The ssid of the WiFi node if available.
   std::string mac_address;  // The mac address of the WiFi node.
   base::Time timestamp;  // Timestamp when this AP was detected.
   int signal_strength;  // Radio signal strength measured in dBm.
   int signal_to_noise;  // Current signal to noise ratio measured in dB.
   int channel;  // Wifi channel number.
+
+  WifiAccessPoint();
+  WifiAccessPoint(const WifiAccessPoint& other);
+  ~WifiAccessPoint();
+
+  bool operator==(const WifiAccessPoint&) const = default;
 };
 
 // Struct for passing cellular location data
 // The age, signalStrength, and timingAdvance fields are currently unused:
 // https://developers.google.com/maps/documentation/geolocation/intro#cell_tower_object
 struct COMPONENT_EXPORT(CHROMEOS_NETWORK) CellTower {
-  CellTower();
-  CellTower(const CellTower& other);
-  ~CellTower();
   std::string mcc;       // The mobile country code if available
   std::string mnc;       // The mobile network code if available
   std::string lac;       // The location area code if available
   std::string ci;        // The cell id if availabe
   base::Time timestamp;  // Timestamp when this location was detected.
+
+  CellTower();
+  CellTower(const CellTower& other);
+  ~CellTower();
+
+  bool operator==(const CellTower&) const = default;
 };
 
 // Struct for passing network scan result data.
diff --git a/chromeos/ash/components/network/portal_detector/BUILD.gn b/chromeos/ash/components/network/portal_detector/BUILD.gn
deleted file mode 100644
index b9ff8507..0000000
--- a/chromeos/ash/components/network/portal_detector/BUILD.gn
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2022 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-assert(is_chromeos, "Non-ChromeOS builds cannot depend on //chromeos/ash")
-
-source_set("portal_detector") {
-  configs += [ "//chromeos/ash/components/network:network_config" ]
-  public_deps = [ "//base" ]
-  deps = [
-    "//components/device_event_log",
-    "//net",
-  ]
-  sources = [
-    "network_portal_detector.cc",
-    "network_portal_detector.h",
-    "network_portal_detector_stub.cc",
-    "network_portal_detector_stub.h",
-  ]
-}
-
-source_set("test_support") {
-  testonly = true
-  public_deps = [ ":portal_detector" ]
-  deps = [ "//testing/gmock" ]
-  sources = [
-    "mock_network_portal_detector.cc",
-    "mock_network_portal_detector.h",
-  ]
-}
diff --git a/chromeos/ash/components/network/portal_detector/DIR_METADATA b/chromeos/ash/components/network/portal_detector/DIR_METADATA
deleted file mode 100644
index f7f2742..0000000
--- a/chromeos/ash/components/network/portal_detector/DIR_METADATA
+++ /dev/null
@@ -1,3 +0,0 @@
-monorail {
-  component: "Internals>Services>Ash"
-}
diff --git a/chromeos/ash/components/network/portal_detector/mock_network_portal_detector.cc b/chromeos/ash/components/network/portal_detector/mock_network_portal_detector.cc
deleted file mode 100644
index 3ff0fe1..0000000
--- a/chromeos/ash/components/network/portal_detector/mock_network_portal_detector.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/ash/components/network/portal_detector/mock_network_portal_detector.h"
-
-namespace ash {
-
-MockNetworkPortalDetector::MockNetworkPortalDetector() = default;
-
-MockNetworkPortalDetector::~MockNetworkPortalDetector() = default;
-
-}  // namespace ash
diff --git a/chromeos/ash/components/network/portal_detector/mock_network_portal_detector.h b/chromeos/ash/components/network/portal_detector/mock_network_portal_detector.h
deleted file mode 100644
index 957ccdb..0000000
--- a/chromeos/ash/components/network/portal_detector/mock_network_portal_detector.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_ASH_COMPONENTS_NETWORK_PORTAL_DETECTOR_MOCK_NETWORK_PORTAL_DETECTOR_H_
-#define CHROMEOS_ASH_COMPONENTS_NETWORK_PORTAL_DETECTOR_MOCK_NETWORK_PORTAL_DETECTOR_H_
-
-#include "chromeos/ash/components/network/portal_detector/network_portal_detector.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace ash {
-
-class MockNetworkPortalDetector : public NetworkPortalDetector {
- public:
-  MockNetworkPortalDetector();
-
-  MockNetworkPortalDetector(const MockNetworkPortalDetector&) = delete;
-  MockNetworkPortalDetector& operator=(const MockNetworkPortalDetector&) =
-      delete;
-
-  ~MockNetworkPortalDetector() override;
-
-  MOCK_METHOD0(IsEnabled, bool());
-  MOCK_METHOD0(Enable, void());
-};
-
-}  // namespace ash
-
-#endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_PORTAL_DETECTOR_MOCK_NETWORK_PORTAL_DETECTOR_H_
diff --git a/chromeos/ash/components/network/portal_detector/network_portal_detector.cc b/chromeos/ash/components/network/portal_detector/network_portal_detector.cc
deleted file mode 100644
index 1b31aad1..0000000
--- a/chromeos/ash/components/network/portal_detector/network_portal_detector.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2014 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/ash/components/network/portal_detector/network_portal_detector.h"
-
-#include "base/logging.h"
-#include "components/device_event_log/device_event_log.h"
-
-namespace ash {
-
-namespace {
-
-bool set_for_testing_ = false;
-NetworkPortalDetector* network_portal_detector_ = nullptr;
-
-}  // namespace
-
-namespace network_portal_detector {
-
-void InitializeForTesting(NetworkPortalDetector* network_portal_detector) {
-  if (network_portal_detector) {
-    CHECK(!set_for_testing_) << "InitializeForTesting is called twice";
-    delete network_portal_detector_;
-    network_portal_detector_ = network_portal_detector;
-    set_for_testing_ = true;
-  } else {
-    network_portal_detector_ = nullptr;
-    set_for_testing_ = false;
-  }
-}
-
-bool IsInitialized() {
-  return network_portal_detector_;
-}
-
-bool SetForTesting() {
-  return set_for_testing_;
-}
-
-void Shutdown() {
-  CHECK(network_portal_detector_ || set_for_testing_)
-      << "Shutdown() called without Initialize()";
-  delete network_portal_detector_;
-  network_portal_detector_ = nullptr;
-}
-
-NetworkPortalDetector* GetInstance() {
-  CHECK(network_portal_detector_) << "GetInstance() called before Initialize()";
-  return network_portal_detector_;
-}
-
-void SetNetworkPortalDetector(NetworkPortalDetector* network_portal_detector) {
-  CHECK(!network_portal_detector_)
-      << "NetworkPortalDetector was initialized twice.";
-  NET_LOG(EVENT) << "SetNetworkPortalDetector";
-  network_portal_detector_ = network_portal_detector;
-}
-
-}  // namespace network_portal_detector
-
-}  // namespace ash
diff --git a/chromeos/ash/components/network/portal_detector/network_portal_detector.h b/chromeos/ash/components/network/portal_detector/network_portal_detector.h
deleted file mode 100644
index 631cbf8..0000000
--- a/chromeos/ash/components/network/portal_detector/network_portal_detector.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2014 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_ASH_COMPONENTS_NETWORK_PORTAL_DETECTOR_NETWORK_PORTAL_DETECTOR_H_
-#define CHROMEOS_ASH_COMPONENTS_NETWORK_PORTAL_DETECTOR_NETWORK_PORTAL_DETECTOR_H_
-
-#include "base/component_export.h"
-
-namespace ash {
-
-// This is an interface class for the chromeos portal detector.
-// See network_portal_detector_impl.h for details.
-class COMPONENT_EXPORT(CHROMEOS_NETWORK) NetworkPortalDetector {
- public:
-  NetworkPortalDetector(const NetworkPortalDetector&) = delete;
-  NetworkPortalDetector& operator=(const NetworkPortalDetector&) = delete;
-
-  virtual ~NetworkPortalDetector() {}
-
-  // Returns true if portal detection is enabled.
-  virtual bool IsEnabled() = 0;
-
-  // Enable portal detection. Once enabled, portal detection for the default
-  // network will be handled any time the default network state changes.
-  virtual void Enable() = 0;
-
- protected:
-  NetworkPortalDetector() {}
-};
-
-// Manages a global NetworkPortalDetector instance that can be accessed across
-// all ChromeOS components.
-namespace network_portal_detector {
-// Gets the instance of the NetworkPortalDetector. Return value should
-// be used carefully in tests, because it can be changed "on the fly"
-// by calls to InitializeForTesting().
-COMPONENT_EXPORT(CHROMEOS_NETWORK) NetworkPortalDetector* GetInstance();
-
-// Returns |true| if NetworkPortalDetector was Initialized and it is safe to
-// call GetInstance.
-COMPONENT_EXPORT(CHROMEOS_NETWORK) bool IsInitialized();
-
-// Deletes the instance of the NetworkPortalDetector.
-COMPONENT_EXPORT(CHROMEOS_NETWORK) void Shutdown();
-
-COMPONENT_EXPORT(CHROMEOS_NETWORK)
-void SetNetworkPortalDetector(NetworkPortalDetector* network_portal_detector);
-
-// Initializes network portal detector for testing. The
-// |network_portal_detector| will be owned by the internal pointer
-// and deleted by Shutdown().
-COMPONENT_EXPORT(CHROMEOS_NETWORK)
-void InitializeForTesting(NetworkPortalDetector* network_portal_detector);
-
-// Returns true if the network portal detector has been set for testing.
-COMPONENT_EXPORT(CHROMEOS_NETWORK) bool SetForTesting();
-
-}  // namespace network_portal_detector
-
-}  // namespace ash
-
-#endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_PORTAL_DETECTOR_NETWORK_PORTAL_DETECTOR_H_
diff --git a/chromeos/ash/components/network/portal_detector/network_portal_detector_stub.cc b/chromeos/ash/components/network/portal_detector/network_portal_detector_stub.cc
deleted file mode 100644
index 1b015c6..0000000
--- a/chromeos/ash/components/network/portal_detector/network_portal_detector_stub.cc
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2015 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/ash/components/network/portal_detector/network_portal_detector_stub.h"
-
-namespace ash {
-
-NetworkPortalDetectorStub::NetworkPortalDetectorStub() = default;
-
-NetworkPortalDetectorStub::~NetworkPortalDetectorStub() = default;
-
-bool NetworkPortalDetectorStub::IsEnabled() {
-  return false;
-}
-
-void NetworkPortalDetectorStub::Enable() {}
-
-}  // namespace ash
diff --git a/chromeos/ash/components/network/portal_detector/network_portal_detector_stub.h b/chromeos/ash/components/network/portal_detector/network_portal_detector_stub.h
deleted file mode 100644
index a6b86ac41..0000000
--- a/chromeos/ash/components/network/portal_detector/network_portal_detector_stub.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2015 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_ASH_COMPONENTS_NETWORK_PORTAL_DETECTOR_NETWORK_PORTAL_DETECTOR_STUB_H_
-#define CHROMEOS_ASH_COMPONENTS_NETWORK_PORTAL_DETECTOR_NETWORK_PORTAL_DETECTOR_STUB_H_
-
-#include "chromeos/ash/components/network/portal_detector/network_portal_detector.h"
-
-namespace ash {
-
-class COMPONENT_EXPORT(CHROMEOS_NETWORK) NetworkPortalDetectorStub
-    : public NetworkPortalDetector {
- public:
-  NetworkPortalDetectorStub();
-
-  NetworkPortalDetectorStub(const NetworkPortalDetectorStub&) = delete;
-  NetworkPortalDetectorStub& operator=(const NetworkPortalDetectorStub&) =
-      delete;
-
-  ~NetworkPortalDetectorStub() override;
-
- private:
-  // NetworkPortalDetector:
-  bool IsEnabled() override;
-  void Enable() override;
-};
-
-}  // namespace ash
-
-#endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_PORTAL_DETECTOR_NETWORK_PORTAL_DETECTOR_STUB_H_
diff --git a/chromeos/ash/components/network/proxy/BUILD.gn b/chromeos/ash/components/network/proxy/BUILD.gn
index 49011b7..42b13a2 100644
--- a/chromeos/ash/components/network/proxy/BUILD.gn
+++ b/chromeos/ash/components/network/proxy/BUILD.gn
@@ -14,7 +14,6 @@
     "//base",
     "//chromeos/ash/components/dbus/shill",
     "//chromeos/ash/components/network/onc",
-    "//chromeos/ash/components/network/portal_detector",
     "//chromeos/constants",
     "//components/onc",
     "//components/pref_registry",
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index f2822ae2f..eed8322 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -157,6 +157,10 @@
 BASE_FEATURE(kFeatureManagementDisableChromeCompose,
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables GLIC on ChromeOS. This flag is intended to be controlled by the
+// feature management module.
+BASE_FEATURE(kFeatureManagementGlic, base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables rounded windows. This flag is intended to be controlled by the
 // feature management module.
 BASE_FEATURE(kFeatureManagementRoundedWindows,
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index 021e4b9..6e530d5 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -73,6 +73,8 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 BASE_DECLARE_FEATURE(kFeatureManagementDisableChromeCompose);
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+BASE_DECLARE_FEATURE(kFeatureManagementGlic);
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 BASE_DECLARE_FEATURE(kFeatureManagementRoundedWindows);
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 BASE_DECLARE_FEATURE(kNotebookLmAppPreinstall);
diff --git a/clank b/clank
index 707d852..8ef6ed3 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 707d85242b4fbacce36e4c6de218dfd44d9a9708
+Subproject commit 8ef6ed3b6b8acee05b6b2f324002eedf21035d63
diff --git a/components/autofill/content/browser/content_autofill_driver.cc b/components/autofill/content/browser/content_autofill_driver.cc
index 4f7a9cdc..cf8031d 100644
--- a/components/autofill/content/browser/content_autofill_driver.cc
+++ b/components/autofill/content/browser/content_autofill_driver.cc
@@ -443,6 +443,11 @@
       network::mojom::PermissionsPolicyFeature::kAutofill);
 }
 
+bool ContentAutofillDriver::IsPolicyControlledFeatureManualTextEnabled() const {
+  return render_frame_host_->IsFeatureEnabled(
+      network::mojom::PermissionsPolicyFeature::kManualText);
+}
+
 bool ContentAutofillDriver::CanShowAutofillUi() const {
   // Don't show AutofillUi for inactive RenderFrameHost. Here it is safe to
   // ignore the calls from inactive RFH as the renderer is not expecting a reply
diff --git a/components/autofill/content/browser/content_autofill_driver.h b/components/autofill/content/browser/content_autofill_driver.h
index cd02f9b1..1ea5b68 100644
--- a/components/autofill/content/browser/content_autofill_driver.h
+++ b/components/autofill/content/browser/content_autofill_driver.h
@@ -170,6 +170,7 @@
   AutofillManager& GetAutofillManager() override;
   ukm::SourceId GetPageUkmSourceId() const override;
   bool IsPolicyControlledFeatureAutofillEnabled() const override;
+  bool IsPolicyControlledFeatureManualTextEnabled() const override;
   bool CanShowAutofillUi() const override;
   std::optional<net::IsolationInfo> GetIsolationInfo() override;
 
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index f4ab274..b961abc 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -644,7 +644,9 @@
   // unclear if this is still needed.
   auto handle_focus_change = [&](base::optional_ref<FormData> extracted_form =
                                      std::nullopt) {
-    if ((config_.uses_keyboard_accessory_for_suggestions ||
+    if (((config_.uses_keyboard_accessory_for_suggestions &&
+          !base::FeatureList::IsEnabled(
+              features::kAutofillAndroidKeyboardAccessoryDynamicPositioning)) ||
          !config_.focus_requires_scroll) &&
         new_focused_element && unsafe_render_frame() &&
         unsafe_render_frame()->GetWebFrame()->HasTransientUserActivation()) {
@@ -1677,6 +1679,20 @@
   std::move(callback).Run(result);
 }
 
+void AutofillAgent::OnDevToolsSessionConnectionChanged(bool attached) {
+  if (!attached) {
+    return;
+  }
+  if (is_dom_content_loaded_) {
+    // Re-extract all forms. Otherwise, ExtractFormsUnthrottled() would emit
+    // DevTools issues only for the updated forms.
+    form_cache_.Reset();
+    ExtractFormsUnthrottled(
+        /*callback=*/{},
+        GetCallTimerState(kOnDevToolsSessionConnectionChanged));
+  }
+}
+
 void AutofillAgent::EmitFormIssuesToDevtools() {
   // TODO(crbug.com/1399414,crbug.com/1444566): Throttle this call if possible.
   ExtractFormsUnthrottled(/*callback=*/{},
@@ -1849,7 +1865,9 @@
     }
   }
 
-  if (!config_.uses_keyboard_accessory_for_suggestions &&
+  if ((!config_.uses_keyboard_accessory_for_suggestions ||
+       base::FeatureList::IsEnabled(
+           features::kAutofillAndroidKeyboardAccessoryDynamicPositioning)) &&
       config_.focus_requires_scroll) {
     HandleFocusChangeComplete(
         /*focused_node_was_last_clicked=*/
@@ -1867,8 +1885,13 @@
       node.Focused() || ((contenteditable = node.RootEditableElement()) &&
                          contenteditable.Focused());
 #if defined(ANDROID)
-  HandleFocusChangeComplete(/*focused_node_was_last_clicked=*/is_focused,
-                            /*form_cache=*/{});
+  if (base::FeatureList::IsEnabled(
+          features::kAutofillAndroidKeyboardAccessoryDynamicPositioning)) {
+    last_left_mouse_down_or_gesture_tap_in_node_caused_focus_ = is_focused;
+  } else {
+    HandleFocusChangeComplete(/*focused_node_was_last_clicked=*/is_focused,
+                              /*form_cache=*/{});
+  }
 #else
   last_left_mouse_down_or_gesture_tap_in_node_caused_focus_ = is_focused;
 #endif
diff --git a/components/autofill/content/renderer/autofill_agent.h b/components/autofill/content/renderer/autofill_agent.h
index d9c47c91..baf5e011 100644
--- a/components/autofill/content/renderer/autofill_agent.h
+++ b/components/autofill/content/renderer/autofill_agent.h
@@ -336,6 +336,7 @@
       const blink::WebFormControlElement& element) override;
   void FormElementReset(const blink::WebFormElement& form) override;
   void PasswordFieldReset(const blink::WebInputElement& element) override;
+  void OnDevToolsSessionConnectionChanged(bool attached) override;
   void EmitFormIssuesToDevtools() override;
 
   // Starts observing the caret in the given element. Previous observers are
diff --git a/components/autofill/content/renderer/timing.cc b/components/autofill/content/renderer/timing.cc
index f749254..49091be 100644
--- a/components/autofill/content/renderer/timing.cc
+++ b/components/autofill/content/renderer/timing.cc
@@ -37,6 +37,8 @@
       return "JavaScriptChangedValue";
     case kNotifyPasswordManagerAboutClearedForm:
       return "NotifyPasswordManagerAboutClearedForm";
+    case kOnDevToolsSessionConnectionChanged:
+      return "OnDevToolsSessionConnectionChanged";
     case kOnProvisionallySaveForm:
       return "OnProvisionallySaveForm";
     case kOnTextFieldValueChanged:
diff --git a/components/autofill/content/renderer/timing.h b/components/autofill/content/renderer/timing.h
index ace996b..e046344 100644
--- a/components/autofill/content/renderer/timing.h
+++ b/components/autofill/content/renderer/timing.h
@@ -37,6 +37,7 @@
     kEmitFormIssuesToDevtools,
     kExtractForms,
     kExtractFormsAndNotifyPasswordAutofillAgent,
+    kOnDevToolsSessionConnectionChanged,
   };
   CallSite call_site = internal::IsRequired();
   base::TimeTicks last_autofill_agent_reset = internal::IsRequired();
diff --git a/components/autofill/core/browser/foundations/autofill_driver.h b/components/autofill/core/browser/foundations/autofill_driver.h
index 2679b1e..feaa3bf 100644
--- a/components/autofill/core/browser/foundations/autofill_driver.h
+++ b/components/autofill/core/browser/foundations/autofill_driver.h
@@ -188,6 +188,11 @@
   // frame may pass it on to its children.
   virtual bool IsPolicyControlledFeatureAutofillEnabled() const = 0;
 
+  // Returns true if the policy-controlled feature "manual-text" is enabled in
+  // the document. In the main frame the permission is enabled by default.
+  // Parent frames may pass it on to its children.
+  virtual bool IsPolicyControlledFeatureManualTextEnabled() const = 0;
+
   // Returns the IsolationInfo of the associated frame. May be nullopt if the
   // IsolationInfo is not used (for example, on iOS).
   virtual std::optional<net::IsolationInfo> GetIsolationInfo() = 0;
diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager.cc b/components/autofill/core/browser/foundations/browser_autofill_manager.cc
index 7ad0486..b98a00f 100644
--- a/components/autofill/core/browser/foundations/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/foundations/browser_autofill_manager.cc
@@ -1703,37 +1703,29 @@
   }
 
   // When a user interacts with the credit card form on the merchant checkout
-  // pages, `this` checks `amount_extraction_manager_` if amount extraction
-  // should happen, and if so, triggers amount extraction.
+  // pages, trigger amount extraction if any suggested feature requires it.
   if (autofill_field) {
-    if (base::FeatureList::IsEnabled(
-            features::kAutofillEnableAiBasedAmountExtraction)) {
-      // TODO(crbug.com/444685282): Add an additional check: if there is no
-      // BNPL suggestion, do not fetch the page content, as BNPL and APC should
-      // be a 1:1 mapping in this case.
-      GetAmountExtractionManager().FetchAiPageContent();
-    } else {
-      const DenseSet<AmountExtractionManager::EligibleFeature>
-          eligible_features = GetAmountExtractionManager().GetEligibleFeatures(
-              client()
-                  .GetPaymentsAutofillClient()
-                  ->IsAutofillPaymentMethodsEnabled(),
-              ShouldSuppressSuggestions(context.suppress_reason, log_manager()),
-              !suggestions.empty(), context.filling_product,
-              autofill_field->Type().GetCreditCardType());
+    const DenseSet<AmountExtractionManager::EligibleFeature> eligible_features =
+        GetAmountExtractionManager().GetEligibleFeatures(
+            client()
+                .GetPaymentsAutofillClient()
+                ->IsAutofillPaymentMethodsEnabled(),
+            ShouldSuppressSuggestions(context.suppress_reason, log_manager()),
+            suggestions, context.filling_product,
+            autofill_field->Type().GetCreditCardType());
 
-      if (!eligible_features.empty()) {
-        for (AmountExtractionManager::EligibleFeature eligible_feature :
-             eligible_features) {
-          switch (eligible_feature) {
-            case AmountExtractionManager::EligibleFeature::kBnpl:
-              GetPaymentsBnplManager()->NotifyOfSuggestionGeneration(
-                  trigger_source);
-              continue;
+    for (AmountExtractionManager::EligibleFeature eligible_feature :
+         eligible_features) {
+      switch (eligible_feature) {
+        case AmountExtractionManager::EligibleFeature::kBnpl:
+          if (base::FeatureList::IsEnabled(
+                  features::kAutofillEnableAiBasedAmountExtraction)) {
+            GetAmountExtractionManager().FetchAiPageContent();
+          } else {
+            GetPaymentsBnplManager()->NotifyOfSuggestionGeneration(
+                trigger_source);
+            GetAmountExtractionManager().TriggerCheckoutAmountExtraction();
           }
-          NOTREACHED();
-        }
-        GetAmountExtractionManager().TriggerCheckoutAmountExtraction();
       }
     }
   }
diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
index 62d2986..c882e03f 100644
--- a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
@@ -982,7 +982,7 @@
               GetEligibleFeatures,
               (bool is_autofill_payments_enabled,
                bool should_suppress_suggestions,
-               bool has_suggestions,
+               const std::vector<Suggestion>& suggestions,
                FillingProduct filling_product,
                FieldType field_type),
               (const, override));
@@ -3274,11 +3274,9 @@
       .WillByDefault(Return(features));
 
   // Verify that the amount extraction is triggered.
-  EXPECT_CALL(amount_extraction_manager(), TriggerCheckoutAmountExtraction)
-      .Times(1);
+  EXPECT_CALL(amount_extraction_manager(), TriggerCheckoutAmountExtraction);
   EXPECT_CALL(*autofill_manager().GetPaymentsBnplManager(),
-              NotifyOfSuggestionGeneration)
-      .Times(1);
+              NotifyOfSuggestionGeneration);
 
   OnAskForValuesToFill(form, card_number_field);
 
@@ -3424,7 +3422,10 @@
   ON_CALL(amount_extraction_manager(), GetEligibleFeatures)
       .WillByDefault(Return(features));
 
-  EXPECT_CALL(amount_extraction_manager(), FetchAiPageContent).Times(1);
+  if constexpr (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
+                BUILDFLAG(IS_CHROMEOS)) {
+    EXPECT_CALL(amount_extraction_manager(), FetchAiPageContent);
+  }
   EXPECT_CALL(amount_extraction_manager(), TriggerCheckoutAmountExtraction)
       .Times(0);
 
@@ -3461,11 +3462,44 @@
       .WillByDefault(Return(features));
 
   EXPECT_CALL(amount_extraction_manager(), FetchAiPageContent).Times(0);
-  EXPECT_CALL(amount_extraction_manager(), TriggerCheckoutAmountExtraction)
-      .Times(1);
+  EXPECT_CALL(amount_extraction_manager(), TriggerCheckoutAmountExtraction);
   EXPECT_CALL(*autofill_manager().GetPaymentsBnplManager(),
-              NotifyOfSuggestionGeneration)
-      .Times(1);
+              NotifyOfSuggestionGeneration);
+
+  OnAskForValuesToFill(form, card_number_field);
+
+  // Verify that suggestions are returned as normal.
+  EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
+}
+
+// Tests that `AmountExtractionManager` should not trigger `FetchAiPageContent`
+// if a credit card form is clicked but there is no BNPL suggestion.
+TEST_F(BrowserAutofillManagerTest,
+       AiAmountExtraction_TriggerPageContentFetch_WithoutBnplSuggestion) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kAutofillEnableAmountExtraction,
+                            features::kAutofillEnableBuyNowPayLaterSyncing,
+                            features::kAutofillEnableBuyNowPayLater,
+                            features::kAutofillEnableAiBasedAmountExtraction},
+      /*disabled_features=*/{});
+  // Set up our form data.
+  FormData form =
+      CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
+  FormsSeen({form});
+
+  // Test case for credit-card-number field.
+  const FormFieldData& card_number_field = form.fields()[1];
+  ASSERT_EQ(card_number_field.name(), u"cardnumber");
+
+  // Verify that `FetchAiPageContent` won't be triggered as there is no BNPL
+  // suggestion. This test case is set up by do not add any BNPL issuer to the
+  // `PaymentsDataManager`.
+  EXPECT_CALL(amount_extraction_manager(), FetchAiPageContent).Times(0);
+
+  ON_CALL(amount_extraction_manager(), GetEligibleFeatures)
+      .WillByDefault(
+          Return(DenseSet<MockAmountExtractionManager::EligibleFeature>{}));
 
   OnAskForValuesToFill(form, card_number_field);
 
@@ -6394,8 +6428,7 @@
   test_api(form_structure).SetFieldTypes({IBAN_VALUE}, {IBAN_VALUE});
 
   EXPECT_CALL(*autofill_client().GetAutofillOptimizationGuideDecider(),
-              OnDidParseForm)
-      .Times(1);
+              OnDidParseForm);
 
   test_api(autofill_manager()).OnFormProcessed(form_data, form_structure);
 }
diff --git a/components/autofill/core/browser/foundations/test_autofill_driver.h b/components/autofill/core/browser/foundations/test_autofill_driver.h
index f73f06b..aae04c0 100644
--- a/components/autofill/core/browser/foundations/test_autofill_driver.h
+++ b/components/autofill/core/browser/foundations/test_autofill_driver.h
@@ -67,6 +67,9 @@
   bool IsPolicyControlledFeatureAutofillEnabled() const override {
     return policy_controlled_feature_autofill_enabled_;
   }
+  bool IsPolicyControlledFeatureManualTextEnabled() const override {
+    return policy_controlled_feature_manual_text_enabled_;
+  }
   bool CanShowAutofillUi() const override { return true; }
   void ApplyFieldAction(mojom::FieldActionType action_type,
                         mojom::ActionPersistence action_persistence,
@@ -162,6 +165,10 @@
     policy_controlled_feature_autofill_enabled_ = enabled;
   }
 
+  void SetPolicyControlledFeatureManualTextEnabled(bool enabled) {
+    policy_controlled_feature_manual_text_enabled_ = enabled;
+  }
+
   void SetIsolationInfo(const net::IsolationInfo& isolation_info) {
     isolation_info_ = isolation_info;
   }
@@ -189,6 +196,7 @@
   bool is_active_ = true;
   bool is_embedded_ = false;
   bool policy_controlled_feature_autofill_enabled_ = false;
+  bool policy_controlled_feature_manual_text_enabled_ = false;
   net::IsolationInfo isolation_info_;
   base::RepeatingCallback<bool(const url::Origin&, FieldGlobalId, FieldType)>
       field_type_map_filter_;
diff --git a/components/autofill/core/browser/payments/amount_extraction_manager.cc b/components/autofill/core/browser/payments/amount_extraction_manager.cc
index e444031..5ab336c9 100644
--- a/components/autofill/core/browser/payments/amount_extraction_manager.cc
+++ b/components/autofill/core/browser/payments/amount_extraction_manager.cc
@@ -4,6 +4,7 @@
 
 #include "components/autofill/core/browser/payments/amount_extraction_manager.h"
 
+#include <algorithm>
 #include <memory>
 #include <optional>
 #include <string>
@@ -111,38 +112,50 @@
 }
 
 DenseSet<AmountExtractionManager::EligibleFeature>
-AmountExtractionManager::GetEligibleFeatures(bool is_autofill_payments_enabled,
-                                             bool should_suppress_suggestions,
-                                             bool has_suggestions,
-                                             FillingProduct filling_product,
-                                             FieldType field_type) const {
-  // If there is an ongoing search, do not trigger the search.
-  if (search_request_pending_) {
-    return {};
-  }
-  // If autofill is not available, do not trigger the search.
-  if (!is_autofill_payments_enabled) {
-    return {};
-  }
+AmountExtractionManager::GetEligibleFeatures(
+    bool is_autofill_payments_enabled,
+    bool should_suppress_suggestions,
+    const std::vector<Suggestion>& suggestions,
+    FillingProduct filling_product,
+    FieldType field_type) const {
+  // In AI-based amount extraction case, if there is a BNPL suggestion present,
+  // then the amount extraction flow should be initiated.
+  if (base::FeatureList::IsEnabled(
+          features::kAutofillEnableAiBasedAmountExtraction)) {
+    if (std::ranges::none_of(suggestions, [](const Suggestion& suggestion) {
+          return suggestion.type == SuggestionType::kBnplEntry;
+        })) {
+      return {};
+    }
+  } else {
+    // If there is an ongoing search, do not trigger the search.
+    if (search_request_pending_) {
+      return {};
+    }
+    // If autofill is not available, do not trigger the search.
+    if (!is_autofill_payments_enabled) {
+      return {};
+    }
 
-  // If the interacted form field is CVC, do not trigger the search.
-  if (kCvcFieldTypes.find(field_type) != kCvcFieldTypes.end()) {
-    return {};
-  }
+    // If the interacted form field is CVC, do not trigger the search.
+    if (kCvcFieldTypes.find(field_type) != kCvcFieldTypes.end()) {
+      return {};
+    }
 
-  // If there are no suggestions, do not trigger the search as suggestions
-  // showing is a requirement for amount extraction.
-  if (!has_suggestions) {
-    return {};
-  }
-  // If there are no suggestions, do not trigger the search as suggestions
-  // showing is a requirement for amount extraction.
-  if (should_suppress_suggestions) {
-    return {};
-  }
-  // Amount extraction is only offered for Credit Card filling scenarios.
-  if (filling_product != FillingProduct::kCreditCard) {
-    return {};
+    // If there are no suggestions, do not trigger the search as suggestions
+    // showing is a requirement for amount extraction.
+    if (suggestions.empty()) {
+      return {};
+    }
+    // If there are no suggestions, do not trigger the search as suggestions
+    // showing is a requirement for amount extraction.
+    if (should_suppress_suggestions) {
+      return {};
+    }
+    // Amount extraction is only offered for Credit Card filling scenarios.
+    if (filling_product != FillingProduct::kCreditCard) {
+      return {};
+    }
   }
 
   const DenseSet<EligibleFeature> eligible_features =
diff --git a/components/autofill/core/browser/payments/amount_extraction_manager.h b/components/autofill/core/browser/payments/amount_extraction_manager.h
index cf10055..be0a7cf5 100644
--- a/components/autofill/core/browser/payments/amount_extraction_manager.h
+++ b/components/autofill/core/browser/payments/amount_extraction_manager.h
@@ -13,6 +13,7 @@
 #include "base/time/time.h"
 #include "components/autofill/core/browser/field_types.h"
 #include "components/autofill/core/browser/filling/filling_product.h"
+#include "components/autofill/core/browser/suggestions/suggestion.h"
 #include "components/autofill/core/common/dense_set.h"
 #include "components/optimization_guide/proto/features/amount_extraction.pb.h"
 
@@ -100,10 +101,11 @@
   //   There is a feature that can use amount extraction on the current
   //   checkout page;
   //   Amount Extraction feature is enabled;
+  //   In the AI-based amount extraction case, if a BNPL suggestion is present;
   virtual DenseSet<EligibleFeature> GetEligibleFeatures(
       bool is_autofill_payments_enabled,
       bool should_suppress_suggestions,
-      bool has_suggestions,
+      const std::vector<Suggestion>& suggestions,
       FillingProduct filling_product,
       FieldType field_type) const;
 
diff --git a/components/autofill/core/browser/payments/amount_extraction_manager_unittest.cc b/components/autofill/core/browser/payments/amount_extraction_manager_unittest.cc
index 9894836..2fe1c136 100644
--- a/components/autofill/core/browser/payments/amount_extraction_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/amount_extraction_manager_unittest.cc
@@ -209,7 +209,9 @@
     EXPECT_THAT(amount_extraction_manager_->GetEligibleFeatures(
                     /*is_autofill_payments_enabled=*/true,
                     /*should_suppress_suggestions=*/false,
-                    /*has_suggestions=*/true,
+                    /*suggestions=*/
+                    std::vector<Suggestion>{
+                        Suggestion(SuggestionType::kCreditCardEntry)},
                     /*filling_product=*/FillingProduct::kCreditCard,
                     /*field_type=*/field_type),
                 ElementsAre(AmountExtractionManager::EligibleFeature::kBnpl));
@@ -220,18 +222,21 @@
   base::test::ScopedFeatureList scoped_feature_list{
       features::kAutofillEnableAmountExtraction};
 
-  EXPECT_THAT(amount_extraction_manager_->GetEligibleFeatures(
-                  /*is_autofill_payments_enabled=*/true,
-                  /*should_suppress_suggestions=*/false,
-                  /*has_suggestions=*/true,
-                  /*filling_product=*/FillingProduct::kCreditCard,
-                  /*field_type=*/FieldType::CREDIT_CARD_VERIFICATION_CODE),
-              IsEmpty());
   EXPECT_THAT(
       amount_extraction_manager_->GetEligibleFeatures(
           /*is_autofill_payments_enabled=*/true,
           /*should_suppress_suggestions=*/false,
-          /*has_suggestions=*/true,
+          /*suggestions=*/
+          std::vector<Suggestion>{Suggestion(SuggestionType::kCreditCardEntry)},
+          /*filling_product=*/FillingProduct::kCreditCard,
+          /*field_type=*/FieldType::CREDIT_CARD_VERIFICATION_CODE),
+      IsEmpty());
+  EXPECT_THAT(
+      amount_extraction_manager_->GetEligibleFeatures(
+          /*is_autofill_payments_enabled=*/true,
+          /*should_suppress_suggestions=*/false,
+          /*suggestions=*/
+          std::vector<Suggestion>{Suggestion(SuggestionType::kCreditCardEntry)},
           /*filling_product=*/FillingProduct::kCreditCard, /*field_type=*/
           FieldType::CREDIT_CARD_STANDALONE_VERIFICATION_CODE),
       IsEmpty());
@@ -244,64 +249,136 @@
                             features::kAutofillEnableBuyNowPayLater},
       /*disabled_features=*/{features::kAutofillEnableAmountExtraction});
 
-  EXPECT_THAT(amount_extraction_manager_->GetEligibleFeatures(
-                  /*is_autofill_payments_enabled=*/true,
-                  /*should_suppress_suggestions=*/false,
-                  /*has_suggestions=*/true,
-                  /*filling_product=*/FillingProduct::kCreditCard,
-                  /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
-              IsEmpty());
+  EXPECT_THAT(
+      amount_extraction_manager_->GetEligibleFeatures(
+          /*is_autofill_payments_enabled=*/true,
+          /*should_suppress_suggestions=*/false,
+          /*suggestions=*/
+          std::vector<Suggestion>{Suggestion(SuggestionType::kCreditCardEntry)},
+          /*filling_product=*/FillingProduct::kCreditCard,
+          /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
+      IsEmpty());
 }
 
 TEST_F(AmountExtractionManagerTest, ShouldNotTriggerWhenSearchIsOngoing) {
   test_api(*amount_extraction_manager_)
       .SetSearchRequestPending(
           /*search_request_pending*/ true);
-  EXPECT_THAT(amount_extraction_manager_->GetEligibleFeatures(
-                  /*is_autofill_payments_enabled=*/true,
-                  /*should_suppress_suggestions=*/false,
-                  /*has_suggestions=*/true,
-                  /*filling_product=*/FillingProduct::kCreditCard,
-                  /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
-              IsEmpty());
+  EXPECT_THAT(
+      amount_extraction_manager_->GetEligibleFeatures(
+          /*is_autofill_payments_enabled=*/true,
+          /*should_suppress_suggestions=*/false,
+          /*suggestions=*/
+          std::vector<Suggestion>{Suggestion(SuggestionType::kCreditCardEntry)},
+          /*filling_product=*/FillingProduct::kCreditCard,
+          /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
+      IsEmpty());
 }
 
 TEST_F(AmountExtractionManagerTest, ShouldNotTriggerWhenAutofillUnavailable) {
+  EXPECT_THAT(
+      amount_extraction_manager_->GetEligibleFeatures(
+          /*is_autofill_payments_enabled=*/false,
+          /*should_suppress_suggestions=*/false,
+          /*suggestions=*/
+          std::vector<Suggestion>{Suggestion(SuggestionType::kCreditCardEntry)},
+          /*filling_product=*/FillingProduct::kCreditCard,
+          /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
+      IsEmpty());
+}
+
+TEST_F(AmountExtractionManagerTest,
+       AiBasedAmountExtractionShouldNotTriggerWhenNoBnplSuggestion) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      features::kAutofillEnableAiBasedAmountExtraction};
+  EXPECT_THAT(
+      amount_extraction_manager_->GetEligibleFeatures(
+          /*is_autofill_payments_enabled=*/true,
+          /*should_suppress_suggestions=*/false,
+          /*suggestions=*/
+          std::vector<Suggestion>{Suggestion(SuggestionType::kCreditCardEntry)},
+          /*filling_product=*/FillingProduct::kCreditCard,
+          /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
+      IsEmpty());
+}
+
+TEST_F(AmountExtractionManagerTest,
+       AiBasedAmountExtractionShouldNotTriggerWhenAutofillDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      features::kAutofillEnableAiBasedAmountExtraction};
   EXPECT_THAT(amount_extraction_manager_->GetEligibleFeatures(
                   /*is_autofill_payments_enabled=*/false,
                   /*should_suppress_suggestions=*/false,
-                  /*has_suggestions=*/true,
+                  /*suggestions=*/std::vector<Suggestion>{},
                   /*filling_product=*/FillingProduct::kCreditCard,
                   /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
               IsEmpty());
 }
 
-TEST_F(AmountExtractionManagerTest, ShouldNotTriggerWhenFormIsNotCreditCard) {
-  EXPECT_THAT(amount_extraction_manager_->GetEligibleFeatures(
-                  /*is_autofill_payments_enabled=*/true,
-                  /*should_suppress_suggestions=*/false,
-                  /*has_suggestions=*/true,
-                  /*filling_product=*/FillingProduct::kAddress,
-                  /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
-              IsEmpty());
-}
-
 TEST_F(AmountExtractionManagerTest,
+       AiBasedAmountExtractionShouldTriggerWhenBnplSuggestionPresent) {
+  base::test::ScopedFeatureList scoped_feature_list{
+      features::kAutofillEnableAiBasedAmountExtraction};
+
+  EXPECT_THAT(
+      amount_extraction_manager_->GetEligibleFeatures(
+          /*is_autofill_payments_enabled=*/true,
+          /*should_suppress_suggestions=*/false,
+          /*suggestions=*/
+          std::vector<Suggestion>{Suggestion(SuggestionType::kBnplEntry)},
+          /*filling_product=*/FillingProduct::kCreditCard,
+          /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
+      // Verifies the set contains exactly this one element
+      testing::UnorderedElementsAre(
+          AmountExtractionManager::EligibleFeature::kBnpl));
+}
+
+TEST_F(
+    AmountExtractionManagerTest,
+    AiBasedAmountExtractionShouldTriggerWhenBnplSuggestionPresentButFeatureDisabled) {
+  EXPECT_THAT(
+      amount_extraction_manager_->GetEligibleFeatures(
+          /*is_autofill_payments_enabled=*/true,
+          /*should_suppress_suggestions=*/false,
+          /*suggestions=*/
+          std::vector<Suggestion>{Suggestion(SuggestionType::kBnplEntry)},
+          /*filling_product=*/FillingProduct::kCreditCard,
+          /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
+      // Verifies the set contains exactly this one element
+      testing::UnorderedElementsAre(
+          AmountExtractionManager::EligibleFeature::kBnpl));
+}
+
+TEST_F(AmountExtractionManagerTest, ShouldNotTriggerWhenFormIsNotCreditCard) {
+  EXPECT_THAT(
+      amount_extraction_manager_->GetEligibleFeatures(
+          /*is_autofill_payments_enabled=*/true,
+          /*should_suppress_suggestions=*/false,
+          /*suggestions=*/
+          std::vector<Suggestion>{Suggestion(SuggestionType::kCreditCardEntry)},
+          /*filling_product=*/FillingProduct::kAddress,
+          /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
+      IsEmpty());
+}
+
+TEST_F(AmountExtractionManagerTest,
        ShouldNotTriggerWhenSuggestionIsSuppressed) {
-  EXPECT_THAT(amount_extraction_manager_->GetEligibleFeatures(
-                  /*is_autofill_payments_enabled=*/true,
-                  /*should_suppress_suggestions=*/true,
-                  /*has_suggestions=*/true,
-                  /*filling_product=*/FillingProduct::kCreditCard,
-                  /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
-              IsEmpty());
+  EXPECT_THAT(
+      amount_extraction_manager_->GetEligibleFeatures(
+          /*is_autofill_payments_enabled=*/true,
+          /*should_suppress_suggestions=*/true,
+          /*suggestions=*/
+          std::vector<Suggestion>{Suggestion(SuggestionType::kCreditCardEntry)},
+          /*filling_product=*/FillingProduct::kCreditCard,
+          /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
+      IsEmpty());
 }
 
 TEST_F(AmountExtractionManagerTest, ShouldNotTriggerWhenNoSuggestion) {
   EXPECT_THAT(amount_extraction_manager_->GetEligibleFeatures(
                   /*is_autofill_payments_enabled=*/true,
                   /*should_suppress_suggestions=*/false,
-                  /*has_suggestions=*/false,
+                  /*suggestions=*/{},
                   /*filling_product=*/FillingProduct::kCreditCard,
                   /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
               IsEmpty());
@@ -314,13 +391,15 @@
       IsUrlEligibleForBnplIssuer)
       .WillByDefault(Return(false));
 
-  EXPECT_THAT(amount_extraction_manager_->GetEligibleFeatures(
-                  /*is_autofill_payments_enabled=*/true,
-                  /*should_suppress_suggestions=*/false,
-                  /*has_suggestions=*/true,
-                  /*filling_product=*/FillingProduct::kCreditCard,
-                  /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
-              IsEmpty());
+  EXPECT_THAT(
+      amount_extraction_manager_->GetEligibleFeatures(
+          /*is_autofill_payments_enabled=*/true,
+          /*should_suppress_suggestions=*/false,
+          /*suggestions=*/
+          std::vector<Suggestion>{Suggestion(SuggestionType::kCreditCardEntry)},
+          /*filling_product=*/FillingProduct::kCreditCard,
+          /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
+      IsEmpty());
 }
 
 TEST_F(AmountExtractionManagerTest, ShouldNotTriggerInIncognitoMode) {
@@ -333,7 +412,9 @@
     EXPECT_THAT(amount_extraction_manager_->GetEligibleFeatures(
                     /*is_autofill_payments_enabled=*/true,
                     /*should_suppress_suggestions=*/false,
-                    /*has_suggestions=*/true,
+                    /*suggestions=*/
+                    std::vector<Suggestion>{
+                        Suggestion(SuggestionType::kCreditCardEntry)},
                     /*filling_product=*/FillingProduct::kCreditCard,
                     /*field_type=*/field_type),
                 IsEmpty());
@@ -343,13 +424,15 @@
 TEST_F(AmountExtractionManagerTest, ShouldNotTriggerIfNoBnplIssuer) {
   payments_data().ClearBnplIssuers();
 
-  EXPECT_THAT(amount_extraction_manager_->GetEligibleFeatures(
-                  /*is_autofill_payments_enabled=*/true,
-                  /*should_suppress_suggestions=*/false,
-                  /*has_suggestions=*/true,
-                  /*filling_product=*/FillingProduct::kCreditCard,
-                  /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
-              IsEmpty());
+  EXPECT_THAT(
+      amount_extraction_manager_->GetEligibleFeatures(
+          /*is_autofill_payments_enabled=*/true,
+          /*should_suppress_suggestions=*/false,
+          /*suggestions=*/
+          std::vector<Suggestion>{Suggestion(SuggestionType::kCreditCardEntry)},
+          /*filling_product=*/FillingProduct::kCreditCard,
+          /*field_type=*/FieldType::CREDIT_CARD_NUMBER),
+      IsEmpty());
 }
 
 // This test checks when the search is triggered,
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index ca2d617..c556105 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -365,7 +365,6 @@
 BASE_FEATURE(kAutofillAndroidDisableSuggestionsOnJSFocus,
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-#if BUILDFLAG(IS_ANDROID)
 // If enabled, on Android, the Autofill keyboard accessory will not be
 // displayed attached to the keyboard but will be placed below or above the
 // focused field. It works only for large form factor devices like tablets or
@@ -373,7 +372,6 @@
 // TODO(crbug.com/438125774): Remove when launched.
 BASE_FEATURE(kAutofillAndroidKeyboardAccessoryDynamicPositioning,
              base::FEATURE_DISABLED_BY_DEFAULT);
-#endif  // BUILDFLAG(IS_ANDROID)
 
 // When enabled, the placeholder is not considered a label fallback on the
 // renderer side anymore. Instead, local heuristic will match regexes against
@@ -773,6 +771,14 @@
 BASE_FEATURE(kAutofillPolicyControlledFeatureAutofill,
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Controls whether Autofill warns about manual text input in cross-origin
+// frames.
+// This feature lives in Autofill code because of its close relationship to
+// `kAutofillCrossOriginAutofill`.
+// TODO(crbug.com/40178859): Enable this feature.
+BASE_FEATURE(kAutofillPolicyControlledFeatureManualText,
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // If the feature is enabled, before triggering suggestion acceptance, the row
 // view checks that a substantial portion of its content was visible for some
 // minimum required period.
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h
index 502f45f..ea9b496f 100644
--- a/components/autofill/core/common/autofill_features.h
+++ b/components/autofill/core/common/autofill_features.h
@@ -117,10 +117,8 @@
 #endif  // BUILDFLAG(IS_ANDROID)
 COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillAndroidDisableSuggestionsOnJSFocus);
-#if BUILDFLAG(IS_ANDROID)
 COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillAndroidKeyboardAccessoryDynamicPositioning);
-#endif  // BUILDFLAG(IS_ANDROID)
 COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillBetterLocalHeuristicPlaceholderSupport);
 COMPONENT_EXPORT(AUTOFILL)
@@ -267,6 +265,8 @@
 COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillPolicyControlledFeatureAutofill);
 COMPONENT_EXPORT(AUTOFILL)
+BASE_DECLARE_FEATURE(kAutofillPolicyControlledFeatureManualText);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillPopupDontAcceptNonVisibleEnoughSuggestion);
 COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillPopupZOrderSecuritySurface);
diff --git a/components/autofill/ios/browser/autofill_driver_ios.h b/components/autofill/ios/browser/autofill_driver_ios.h
index dd20ed6a..02faaa8 100644
--- a/components/autofill/ios/browser/autofill_driver_ios.h
+++ b/components/autofill/ios/browser/autofill_driver_ios.h
@@ -94,6 +94,7 @@
   BrowserAutofillManager& GetAutofillManager() override;
   ukm::SourceId GetPageUkmSourceId() const override;
   bool IsPolicyControlledFeatureAutofillEnabled() const override;
+  bool IsPolicyControlledFeatureManualTextEnabled() const override;
   bool CanShowAutofillUi() const override;
   base::flat_set<FieldGlobalId> ApplyFormAction(
       mojom::FormActionType action_type,
diff --git a/components/autofill/ios/browser/autofill_driver_ios.mm b/components/autofill/ios/browser/autofill_driver_ios.mm
index ec90a81..e614a08d 100644
--- a/components/autofill/ios/browser/autofill_driver_ios.mm
+++ b/components/autofill/ios/browser/autofill_driver_ios.mm
@@ -238,6 +238,10 @@
   return false;
 }
 
+bool AutofillDriverIOS::IsPolicyControlledFeatureManualTextEnabled() const {
+  return true;
+}
+
 bool AutofillDriverIOS::CanShowAutofillUi() const {
   return true;
 }
diff --git a/components/autofill/ios/browser/resources/autofill_controller.js b/components/autofill/ios/browser/resources/autofill_controller.js
index 5799c16..b9b99629 100644
--- a/components/autofill/ios/browser/resources/autofill_controller.js
+++ b/components/autofill/ios/browser/resources/autofill_controller.js
@@ -5,10 +5,12 @@
 import * as fill_constants from '//components/autofill/ios/form_util/resources/fill_constants.js';
 import * as inferenceUtil from '//components/autofill/ios/form_util/resources/fill_element_inference_util.js';
 import * as fillUtil from '//components/autofill/ios/form_util/resources/fill_util.js';
+import {webFormElementToFormData} from '//components/autofill/ios/form_util/resources/fill_web_form.js';
 import {getFormControlElements, getFormElementFromIdentifier} from '//components/autofill/ios/form_util/resources/form_utils.js';
 import {gCrWeb, gCrWebLegacy} from '//ios/web/public/js_messaging/resources/gcrweb.js';
 import {isTextField, sendWebKitMessage, trim} from '//ios/web/public/js_messaging/resources/utils.js';
 
+
 /**
  * @fileoverview Installs Autofill management functions on the __gCrWeb object.
  *
@@ -319,8 +321,7 @@
     window.setTimeout(() => {
       let formData = new __gCrWeb['common'].JSONSafeObject();
       if (_form) {
-        if (!__gCrWeb.fill.webFormElementToFormData(
-                window, _form, null, formData, /*field=*/ null)) {
+        if (!webFormElementToFormData(window, _form, null, formData)) {
           formData = null;
         }
       } else {
@@ -490,8 +491,8 @@
     }
 
     const form = new __gCrWeb['common'].JSONSafeObject();
-    if (!__gCrWeb.fill.webFormElementToFormData(
-            window, formElement, null, form, /*field=*/ null,
+    if (!webFormElementToFormData(
+            window, formElement, null, form, /*field=*/ undefined,
             canExtractChildFrames())) {
       continue;
     }
diff --git a/components/autofill/ios/form_util/resources/fill.ts b/components/autofill/ios/form_util/resources/fill.ts
index 869a0207..a21e07d 100644
--- a/components/autofill/ios/form_util/resources/fill.ts
+++ b/components/autofill/ios/form_util/resources/fill.ts
@@ -7,8 +7,7 @@
 import * as fillConstants from '//components/autofill/ios/form_util/resources/fill_constants.js';
 import * as inferenceUtil from '//components/autofill/ios/form_util/resources/fill_element_inference_util.js';
 import * as fillUtil from '//components/autofill/ios/form_util/resources/fill_util.js';
-import {formOrFieldsetsToFormData, getFrameUrlOrOrigin} from '//components/autofill/ios/form_util/resources/fill_web_form.js';
-import {getFormControlElements, getFormIdentifier} from '//components/autofill/ios/form_util/resources/form_utils.js';
+import {formOrFieldsetsToFormData, getFrameUrlOrOrigin, webFormElementToFormData} from '//components/autofill/ios/form_util/resources/fill_web_form.js';
 import {gCrWeb, gCrWebLegacy} from '//ios/web/public/js_messaging/resources/gcrweb.js';
 import {isTextField} from '//ios/web/public/js_messaging/resources/utils.js';
 
@@ -23,92 +22,6 @@
 const autofillFormFeaturesApi =
   gCrWeb.getRegisteredApi('autofill_form_features');
 
-declare global {
-  // Defines an additional property, `__gcrweb`, on the Window object.
-  // This definition is needed in order to call into gCrWeb inside an iframe.
-  interface Window {
-    __gCrWeb: any;
-  }
-}
-
-/**
- * Fills |form| with the form data object corresponding to the
- * |formElement|. If |field| is non-NULL, also fills |field| with the
- * FormField object corresponding to the |formControlElement|.
- * |extract_mask| controls what data is extracted.
- * Returns true if |form| is filled out. Returns false if there are no
- * fields or too many fields in the |form|.
- *
- * It is based on the logic in
- *     bool WebFormElementToFormData(
- *         const blink::WebFormElement& form_element,
- *         const blink::WebFormControlElement& form_control_element,
- *         ExtractMask extract_mask,
- *         FormData* form,
- *         FormFieldData* field)
- * in
- * chromium/src/components/autofill/content/renderer/form_autofill_util.cc
- *
- * @param frame The window or frame where the
- *     formElement is in.
- * @param formElement The form element that will be processed.
- * @param formControlElement A control element in
- *     formElement, the FormField of which will be returned in field.
- * @param form Form to fill in the AutofillFormData
- *     information of formElement.
- * @param field Field to fill in the form field
- *     information of formControlElement.
- * @return Whether there are fields and not too many fields in the
- *     form.
- */
-gCrWebLegacy.fill.webFormElementToFormData = function(
-    frame: Window, formElement: HTMLFormElement,
-    formControlElement: fillConstants.FormControlElement,
-    form: fillUtil.AutofillFormData, field?: fillUtil.AutofillFormFieldData,
-    extractChildFrames: boolean = true): boolean {
-  if (!frame) {
-    return false;
-  }
-
-  form.name = getFormIdentifier(formElement);
-  form.origin = getFrameUrlOrOrigin(frame);
-  form.action = fillUtil.getCanonicalActionForForm(formElement);
-
-  // The raw name and id attributes, which may be empty.
-  form.name_attribute = formElement.getAttribute('name') || '';
-  form.id_attribute = formElement.getAttribute('id') || '';
-
-  form.renderer_id = fillUtil.getUniqueID(formElement);
-
-  form.host_frame = frame.__gCrWeb.getFrameId();
-
-  // Note different from form_autofill_util.cc version of this method, which
-  // computes |form.action| using document.completeURL(form_element.action())
-  // and falls back to formElement.action() if the computed action is invalid,
-  // here the action returned by |absoluteURL_| is always valid, which is
-  // computed by creating a <a> element, and we don't check if the action is
-  // valid.
-
-  const controlElements = getFormControlElements(formElement);
-
-  let iframeElements = extractChildFrames &&
-    autofillFormFeaturesApi.getFunction('isAutofillAcrossIframesEnabled')() ?
-    gCrWebLegacy.form.getIframeElements(formElement) :
-      [];
-
-  // To avoid performance bottlenecks, do not keep child frames if their
-  // quantity exceeds the allowed threshold.
-  if (iframeElements.length > fillConstants.MAX_EXTRACTABLE_FRAMES &&
-    autofillFormFeaturesApi.getFunction('isAutofillAcrossIframesThrottlingEnabled')()) {
-      iframeElements = [];
-  }
-  // TODO(crbug.com/454044167): Cleanup autofill TS type casting.
-  return formOrFieldsetsToFormData(
-      formElement, formControlElement, /*fieldsets=*/[],
-      controlElements as fillConstants.FormControlElement[], iframeElements,
-      form, field);
-};
-
 /**
  * Fills out a FormField object from a given form control element.
  *
@@ -225,7 +138,7 @@
 gCrWebLegacy.fill.autofillSubmissionData =
     function(form: HTMLFormElement): fillUtil.AutofillFormData {
   const formData = new gCrWebLegacy['common'].JSONSafeObject();
-      gCrWebLegacy['fill'].webFormElementToFormData(window, form, null, formData, null);
+  webFormElementToFormData(window, form, null, formData);
   return formData;
 };
 
diff --git a/components/autofill/ios/form_util/resources/fill_util_test.ts b/components/autofill/ios/form_util/resources/fill_util_test.ts
index fb4bcb2e..aaf5045f 100644
--- a/components/autofill/ios/form_util/resources/fill_util_test.ts
+++ b/components/autofill/ios/form_util/resources/fill_util_test.ts
@@ -6,6 +6,7 @@
 import * as elementInferenceUtil from '//components/autofill/ios/form_util/resources/fill_element_inference.js';
 import * as inferenceUtil from '//components/autofill/ios/form_util/resources/fill_element_inference_util.js';
 import * as fillUtil from '//components/autofill/ios/form_util/resources/fill_util.js';
+import {webFormElementToFormData} from '//components/autofill/ios/form_util/resources/fill_web_form.js';
 import {CrWebApi, gCrWeb} from '//ios/web/public/js_messaging/resources/gcrweb.js';
 
 /**
@@ -54,6 +55,7 @@
 fillApi.addFunction('registerAllChildFrames', registerAllChildFrames);
 fillApi.addFunction('setInputElementValue', fillUtil.setInputElementValue);
 fillApi.addFunction('shouldAutocomplete', fillUtil.shouldAutocomplete);
+fillApi.addFunction('webFormElementToFormData', webFormElementToFormData);
 // go/keep-sorted end
 
 
diff --git a/components/autofill/ios/form_util/resources/fill_web_form.ts b/components/autofill/ios/form_util/resources/fill_web_form.ts
index 11875dd..629db28 100644
--- a/components/autofill/ios/form_util/resources/fill_web_form.ts
+++ b/components/autofill/ios/form_util/resources/fill_web_form.ts
@@ -2,15 +2,32 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import type {FormControlElement} from '//components/autofill/ios/form_util/resources/fill_constants.js';
-import {CHILD_FRAME_REMOTE_TOKEN_ATTRIBUTE, MAX_DATA_LENGTH, MAX_EXTRACTABLE_FIELDS} from '//components/autofill/ios/form_util/resources/fill_constants.js';
 import {registerChildFrame} from '//components/autofill/ios/form_util/resources/child_frame_registration_lib.js';
+import type {FormControlElement} from '//components/autofill/ios/form_util/resources/fill_constants.js';
+import {CHILD_FRAME_REMOTE_TOKEN_ATTRIBUTE, MAX_DATA_LENGTH, MAX_EXTRACTABLE_FIELDS, MAX_EXTRACTABLE_FRAMES} from '//components/autofill/ios/form_util/resources/fill_constants.js';
 import {inferLabelForElement, inferLabelFromNext} from '//components/autofill/ios/form_util/resources/fill_element_inference.js';
 import {findChildText, isAutofillableElement} from '//components/autofill/ios/form_util/resources/fill_element_inference_util.js';
 import type {AutofillFormData, AutofillFormFieldData, FrameTokenWithPredecessor} from '//components/autofill/ios/form_util/resources/fill_util.js';
+import {getCanonicalActionForForm, getUniqueID} from '//components/autofill/ios/form_util/resources/fill_util.js';
+import {getFormControlElements, getFormIdentifier} from '//components/autofill/ios/form_util/resources/form_utils.js';
 import {gCrWeb, gCrWebLegacy} from '//ios/web/public/js_messaging/resources/gcrweb.js';
 import {removeQueryAndReferenceFromURL} from '//ios/web/public/js_messaging/resources/utils.js';
 
+/**
+ * Retrieves the registered 'autofill_form_features' CrWebApi
+ * instance for use in this file.
+ */
+const autofillFormFeaturesApi =
+    gCrWeb.getRegisteredApi('autofill_form_features');
+
+declare global {
+  // Defines an additional property, `__gcrweb`, on the Window object.
+  // This definition is needed in order to call into gCrWeb inside an iframe.
+  interface Window {
+    __gCrWeb: any;
+  }
+}
+
 // Returns the URL for the frame to be set in the FormData.
 export function getFrameUrlOrOrigin(frame: Window): string {
   if ((frame === frame.top) ||
@@ -26,6 +43,87 @@
 }
 
 /**
+ * Fills |form| with the form data object corresponding to the
+ * |formElement|. If |field| is non-NULL, also fills |field| with the
+ * FormField object corresponding to the |formControlElement|.
+ * |extract_mask| controls what data is extracted.
+ * Returns true if |form| is filled out. Returns false if there are no
+ * fields or too many fields in the |form|.
+ *
+ * It is based on the logic in
+ *     bool WebFormElementToFormData(
+ *         const blink::WebFormElement& form_element,
+ *         const blink::WebFormControlElement& form_control_element,
+ *         ExtractMask extract_mask,
+ *         FormData* form,
+ *         FormFieldData* field)
+ * in
+ * chromium/src/components/autofill/content/renderer/form_autofill_util.cc
+ *
+ * @param frame The window or frame where the
+ *     formElement is in.
+ * @param formElement The form element that will be processed.
+ * @param formControlElement A control element in
+ *     formElement, the FormField of which will be returned in field.
+ * @param form Form to fill in the AutofillFormData
+ *     information of formElement.
+ * @param field Field to fill in the form field
+ *     information of formControlElement.
+ * @return Whether there are fields and not too many fields in the
+ *     form.
+ */
+export function webFormElementToFormData(
+    frame: Window, formElement: HTMLFormElement,
+    formControlElement: FormControlElement|null, form: AutofillFormData,
+    field?: AutofillFormFieldData,
+    extractChildFrames: boolean = true): boolean {
+  if (!frame) {
+    return false;
+  }
+
+  form.name = getFormIdentifier(formElement);
+  form.origin = getFrameUrlOrOrigin(frame);
+  form.action =
+      formElement !== null ? getCanonicalActionForForm(formElement) : '';
+
+  // The raw name and id attributes, which may be empty.
+  form.name_attribute = formElement?.getAttribute('name') || '';
+  form.id_attribute = formElement?.getAttribute('id') || '';
+
+  form.renderer_id = getUniqueID(formElement);
+
+  form.host_frame = frame.__gCrWeb.getFrameId();
+
+  // Note different from form_autofill_util.cc version of this method, which
+  // computes |form.action| using document.completeURL(form_element.action())
+  // and falls back to formElement.action() if the computed action is invalid,
+  // here the action returned by |absoluteURL_| is always valid, which is
+  // computed by creating a <a> element, and we don't check if the action is
+  // valid.
+
+  const controlElements =
+      getFormControlElements(formElement) as FormControlElement[];
+
+  let iframeElements = extractChildFrames &&
+          autofillFormFeaturesApi.getFunction(
+              'isAutofillAcrossIframesEnabled')() ?
+      gCrWebLegacy.form.getIframeElements(formElement) :
+      [];
+
+  // To avoid performance bottlenecks, do not keep child frames if their
+  // quantity exceeds the allowed threshold.
+  if (iframeElements.length > MAX_EXTRACTABLE_FRAMES &&
+      autofillFormFeaturesApi.getFunction(
+          'isAutofillAcrossIframesThrottlingEnabled')()) {
+    iframeElements = [];
+  }
+
+  return formOrFieldsetsToFormData(
+      formElement, formControlElement, /*fieldsets=*/[], controlElements,
+      iframeElements, form, field);
+}
+
+/**
  * Common function shared by webFormElementToFormData() and
  * unownedFormElementsAndFieldSetsToFormData(). Either pass in:
  * 1) |formElement|, |formControlElement| and an empty |fieldsets|.
diff --git a/components/browser_ui/accessibility/android/java/res/layout/page_zoom_indicator_view.xml b/components/browser_ui/accessibility/android/java/res/layout/page_zoom_indicator_view.xml
index 8dcb30c..ce87f4a 100644
--- a/components/browser_ui/accessibility/android/java/res/layout/page_zoom_indicator_view.xml
+++ b/components/browser_ui/accessibility/android/java/res/layout/page_zoom_indicator_view.xml
@@ -9,6 +9,8 @@
     android:layout_width="@dimen/page_zoom_indicator_popup_width"
     android:layout_height="@dimen/page_zoom_indicator_popup_height"
     android:accessibilityPaneTitle="@string/page_zoom_popup_window_content_description"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
     android:gravity="center"
     android:orientation="horizontal"
     android:background="@drawable/page_zoom_background">
diff --git a/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomIndicatorCoordinator.java b/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomIndicatorCoordinator.java
index 36e8bdb0..a1277e6 100644
--- a/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomIndicatorCoordinator.java
+++ b/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomIndicatorCoordinator.java
@@ -17,6 +17,7 @@
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.accessibility.AccessibilityState;
 
 import java.util.function.Supplier;
 
@@ -161,7 +162,6 @@
      */
     @SuppressWarnings("WrongConstant")
     private void sendPaneChangeAccessibilityEvent(boolean isShowing) {
-        assert mView != null : "View is null.";
         AccessibilityEvent event =
                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
         if (isShowing) {
@@ -170,8 +170,6 @@
             event.setContentChangeTypes(
                     AccessibilityEventCompat.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
         }
-        if (mView.getParent() != null) {
-            mView.getParent().requestSendAccessibilityEvent(mView, event);
-        }
+        AccessibilityState.sendAccessibilityEvent(event);
     }
 }
diff --git a/components/browser_ui/strings/android/browser_ui_strings.grd b/components/browser_ui/strings/android/browser_ui_strings.grd
index b53d348..911e74d 100644
--- a/components/browser_ui/strings/android/browser_ui_strings.grd
+++ b/components/browser_ui/strings/android/browser_ui_strings.grd
@@ -1332,6 +1332,15 @@
       <message name="IDS_EDUCATIONAL_TIP_HISTORY_SYNC_BUTTON_CONTINUE" desc="Text on the clickable button on the history sync promo that invites users to turn on history sync in the educational tip module on NTP.">
         Continue
       </message>
+      <message name="IDS_EDUCATIONAL_TIP_TIPS_NOTIFICATIONS_TITLE" desc="Title of the tips notifications promo that invites users to turn on tips notifications in the educational tip module on NTP.">
+        Tips tailored just for you
+      </message>
+      <message name="IDS_EDUCATIONAL_TIP_TIPS_NOTIFICATIONS_DESCRIPTION" desc="Description of the tips notifications promo that invites users to turn on tips notifications in the educational tip module on NTP.">
+        Get notifications for insights that enhance your experience on Chrome
+      </message>
+      <message name="IDS_EDUCATIONAL_TIP_TIPS_NOTIFICATIONS_BUTTON" desc="Text on the clickable button on the tips notifications promo that invites users to turn on tips notifications in the educational tip module on NTP.">
+        Enable
+      </message>
 
       <!-- Safety Hub Magic Stack strings -->
       <message name="IDS_SAFETY_HUB_MAGIC_STACK_MODULE_NAME" desc="Title for the Magic Stack card for all Safety Check modules.">
diff --git a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_EDUCATIONAL_TIP_TIPS_NOTIFICATIONS_BUTTON.png.sha1 b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_EDUCATIONAL_TIP_TIPS_NOTIFICATIONS_BUTTON.png.sha1
new file mode 100644
index 0000000..b4bb159
--- /dev/null
+++ b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_EDUCATIONAL_TIP_TIPS_NOTIFICATIONS_BUTTON.png.sha1
@@ -0,0 +1 @@
+dbc0626c149542c7e7fb02270660e557e4736ea3
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_EDUCATIONAL_TIP_TIPS_NOTIFICATIONS_DESCRIPTION.png.sha1 b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_EDUCATIONAL_TIP_TIPS_NOTIFICATIONS_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..b4bb159
--- /dev/null
+++ b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_EDUCATIONAL_TIP_TIPS_NOTIFICATIONS_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+dbc0626c149542c7e7fb02270660e557e4736ea3
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_EDUCATIONAL_TIP_TIPS_NOTIFICATIONS_TITLE.png.sha1 b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_EDUCATIONAL_TIP_TIPS_NOTIFICATIONS_TITLE.png.sha1
new file mode 100644
index 0000000..b4bb159
--- /dev/null
+++ b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_EDUCATIONAL_TIP_TIPS_NOTIFICATIONS_TITLE.png.sha1
@@ -0,0 +1 @@
+dbc0626c149542c7e7fb02270660e557e4736ea3
\ No newline at end of file
diff --git a/components/contextual_tasks/public/features.cc b/components/contextual_tasks/public/features.cc
index ab92e653..7a8cb40 100644
--- a/components/contextual_tasks/public/features.cc
+++ b/components/contextual_tasks/public/features.cc
@@ -18,6 +18,11 @@
 // Enables relevant context determination for contextual tasks.
 BASE_FEATURE(kContextualTasksContext, base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables context menu settings for contextual tasks.
+BASE_FEATURE(kContextualTasksContextMenu,
+             "ContextualTasksContextMenu",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // The base URL for the AI page.
 const base::FeatureParam<std::string> kContextualTasksAiPageUrl{
     &kContextualTasksContext, "ai-page-url",
@@ -77,6 +82,26 @@
                            base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
 }
 
+bool GetIsContextualTasksNextboxContextMenuEnabled() {
+  return base::FeatureList::IsEnabled(kContextualTasksContextMenu);
+}
+
+const base::FeatureParam<std::string> kContextualTasksNextboxImageFileTypes{
+    &kContextualTasksContextMenu, "ContextualTasksNextboxImageFileTypes",
+    "image/jpeg,image/png"};
+
+const base::FeatureParam<std::string> kContextualTasksNextboxAttachmentFileTypes{
+    &kContextualTasksContextMenu, "ContextualTasksNextboxAttachmentFileTypes",
+    "text/plain,application/pdf"};
+
+const base::FeatureParam<int> kContextualTasksNextboxMaxFileSize{
+    &kContextualTasksContextMenu, "ContextualTasksNextboxMaxFileSize",
+    20 * 1024 * 1024};
+
+const base::FeatureParam<int> kContextualTasksNextboxMaxFileCount{
+    &kContextualTasksContextMenu, "ContextualTasksNextboxMaxFileCount", 4};
+
+
 bool GetEnableLensInContextualTasks() {
   return base::FeatureList::IsEnabled(kContextualTasks) &&
          kEnableLensInContextualTasks.Get();
diff --git a/components/contextual_tasks/public/features.h b/components/contextual_tasks/public/features.h
index c324a45..8f7d7f5 100644
--- a/components/contextual_tasks/public/features.h
+++ b/components/contextual_tasks/public/features.h
@@ -16,6 +16,9 @@
 BASE_DECLARE_FEATURE(kContextualTasks);
 BASE_DECLARE_FEATURE(kContextualTasksContext);
 
+// Enables context menu settings for contextual tasks.
+BASE_DECLARE_FEATURE(kContextualTasksContextMenu);
+
 // Enum denoting which entry point can show when enabled.
 enum class EntryPointOption {
   kNoEntryPoint,
@@ -41,6 +44,21 @@
 // the side panel only affects the current tab.
 extern const base::FeatureParam<bool> kTaskScopedSidePanel;
 
+// Whether the context menu is enabled for Nextbox.
+extern bool GetIsContextualTasksNextboxContextMenuEnabled();
+
+// The file types that can be attached to a Nextbox as images.
+extern const base::FeatureParam<std::string> kContextualTasksNextboxImageFileTypes;
+
+// The file types that can be attached to a Nextbox as attachments.
+extern const base::FeatureParam<std::string> kContextualTasksNextboxAttachmentFileTypes;
+
+// The maximum size of a file that can be attached to a Nextbox.
+extern const base::FeatureParam<int> kContextualTasksNextboxMaxFileSize;
+
+// The maximum number of files that can be attached to a Nextbox.
+extern const base::FeatureParam<int> kContextualTasksNextboxMaxFileCount;
+
 // The user agent suffix to use for requests from the contextual tasks UI.
 extern const base::FeatureParam<std::string> kContextualTasksUserAgentSuffix;
 
diff --git a/components/cronet/gn2bp/templates/boringssl_Android.bp.template b/components/cronet/gn2bp/templates/boringssl_Android.bp.template
index 0d9d61a1..a1234ac 100644
--- a/components/cronet/gn2bp/templates/boringssl_Android.bp.template
+++ b/components/cronet/gn2bp/templates/boringssl_Android.bp.template
@@ -63,6 +63,8 @@
         "-DBORINGSSL_SHARED_LIBRARY",
         "-DBORINGSSL_ANDROID_SYSTEM",
         // Chromium uses extensive harderning mode, so setting the same for boringssl.
+        // This should override any platform default.
+        "-U_LIBCPP_HARDENING_MODE",
         "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE",
         "-DOPENSSL_SMALL",
         "-Werror",
diff --git a/components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request_unittest.cc b/components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request_unittest.cc
index 49eb20d..8d95f7ba 100644
--- a/components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request_unittest.cc
+++ b/components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request_unittest.cc
@@ -103,7 +103,7 @@
   request_details_full->instrument_id_ = 13;
 
   EXPECT_DEATH_IF_SUPPORTED(
-      std::make_unique<FacilitatedPaymentsInitiatePaymentRequest>(
+      std::ignore = std::make_unique<FacilitatedPaymentsInitiatePaymentRequest>(
           std::move(request_details_full),
           /*response_callback=*/base::DoNothing(),
           /*app_locale=*/"US", /*full_sync_enabled=*/true),
diff --git a/components/history_embeddings/vector_database_unittest.cc b/components/history_embeddings/vector_database_unittest.cc
index 7280c539..2025d68 100644
--- a/components/history_embeddings/vector_database_unittest.cc
+++ b/components/history_embeddings/vector_database_unittest.cc
@@ -52,7 +52,7 @@
 }  // namespace
 
 TEST(HistoryEmbeddingsVectorDatabaseTest, Constructs) {
-  std::make_unique<VectorDatabaseInMemory>();
+  std::ignore = std::make_unique<VectorDatabaseInMemory>();
 }
 
 TEST(HistoryEmbeddingsVectorDatabaseTest, EraseNonAsciiCharacters) {
diff --git a/components/infobars/core/infobar_container_with_priority.cc b/components/infobars/core/infobar_container_with_priority.cc
index 0274ac7..0d15543f 100644
--- a/components/infobars/core/infobar_container_with_priority.cc
+++ b/components/infobars/core/infobar_container_with_priority.cc
@@ -52,6 +52,11 @@
 
 void InfoBarContainerWithPriority::ChangeInfoBarManager(
     InfoBarManager* infobar_manager) {
+  if (!IsInfobarPrioritizationEnabled()) {
+    InfoBarContainer::ChangeInfoBarManager(infobar_manager);
+    return;
+  }
+
   scoped_observation_.Reset();
 
   bool state_changed = !infobars().empty();
@@ -96,6 +101,11 @@
 }
 
 void InfoBarContainerWithPriority::OnInfoBarAdded(InfoBar* infobar) {
+  if (!IsInfobarPrioritizationEnabled()) {
+    InfoBarContainer::OnInfoBarAdded(infobar);
+    return;
+  }
+
   const auto priority = infobar->delegate()
                             ? infobar->delegate()->GetPriority()
                             : InfoBarDelegate::InfobarPriority::kDefault;
@@ -104,19 +114,37 @@
 
 void InfoBarContainerWithPriority::OnInfoBarRemoved(InfoBar* infobar,
                                                     bool animate) {
-  // Stop tracking the removed infobar in whichever bucket it was
-  // stored (critical/default/low).
-  ClearVisible(infobar);
+  if (!IsInfobarPrioritizationEnabled()) {
+    InfoBarContainer::OnInfoBarRemoved(infobar, animate);
+    return;
+  }
 
-  // Try to surface the next best candidate in the queue, honoring caps
-  // and priority precedence.
-  Promote();
+  // An infobar is being removed from the manager. It could be in our visible
+  // list or our pending list. We must remove it from whichever list it's in
+  // to prevent holding a dangling pointer.
+  std::erase_if(pending_infobars_, [infobar](const PendingInfoBarEntry& entry) {
+    return entry.infobar == infobar;
+  });
 
+  const size_t removed_from_visible = ClearVisible(infobar);
+
+  // Only try to promote a new infobar if a visible slot was actually freed.
+  if (removed_from_visible > 0) {
+    Promote();
+  }
+
+  // The base class needs to be called to handle the actual view removal and
+  // animation.
   InfoBarContainer::OnInfoBarRemoved(infobar, animate);
 }
 
 void InfoBarContainerWithPriority::OnInfoBarReplaced(InfoBar* old_infobar,
                                                      InfoBar* new_infobar) {
+  if (!IsInfobarPrioritizationEnabled()) {
+    InfoBarContainer::OnInfoBarReplaced(old_infobar, new_infobar);
+    return;
+  }
+
   // Track whether the old infobar was actually visible in this container.
   const bool was_visible =
       std::ranges::any_of(visible_, [old_infobar](const VisibleEntry& entry) {
@@ -297,8 +325,8 @@
   visible_.push_back({.infobar = infobar, .priority = priority});
 }
 
-void InfoBarContainerWithPriority::ClearVisible(InfoBar* infobar) {
-  std::erase_if(visible_, [infobar](const VisibleEntry& entry) {
+size_t InfoBarContainerWithPriority::ClearVisible(InfoBar* infobar) {
+  return std::erase_if(visible_, [infobar](const VisibleEntry& entry) {
     return entry.infobar == infobar;
   });
 }
diff --git a/components/infobars/core/infobar_container_with_priority.h b/components/infobars/core/infobar_container_with_priority.h
index 782d508..d08cc723 100644
--- a/components/infobars/core/infobar_container_with_priority.h
+++ b/components/infobars/core/infobar_container_with_priority.h
@@ -104,7 +104,8 @@
 
   // Removes an infobar from whatever visible priority it belongs to (critical,
   // default, or low). Safe to call even if the infobar was not tracked.
-  void ClearVisible(InfoBar* infobar);
+  // Returns the number of elements removed.
+  size_t ClearVisible(InfoBar* infobar);
 
   // Return the number of infobar currently visible for the given priority.
   size_t CountVisible(InfoBarDelegate::InfobarPriority priority) const;
diff --git a/components/infobars/core/infobar_delegate.h b/components/infobars/core/infobar_delegate.h
index d2682c12..49f790e4 100644
--- a/components/infobars/core/infobar_delegate.h
+++ b/components/infobars/core/infobar_delegate.h
@@ -199,7 +199,7 @@
     LOCAL_TEST_POLICIES_APPLIED_INFOBAR = 115,
     BIDDING_AND_AUCTION_CONSENTED_DEBUGGING_DELEGATE = 116,
     // Removed: PARCEL_TRACKING_INFOBAR_DELEGATE = 117,
-    TEST_THIRD_PARTY_COOKIE_PHASEOUT_DELEGATE = 118,
+    // Removed: TEST_THIRD_PARTY_COOKIE_PHASEOUT_DELEGATE = 118,
     ENABLE_LINK_CAPTURING_INFOBAR_DELEGATE = 119,
     DEV_TOOLS_SHARED_PROCESS_DELEGATE = 120,
     ENHANCED_SAFE_BROWSING_INFOBAR_DELEGATE = 121,
diff --git a/components/js_injection/OWNERS b/components/js_injection/OWNERS
new file mode 100644
index 0000000..89763ab
--- /dev/null
+++ b/components/js_injection/OWNERS
@@ -0,0 +1 @@
+file://android_webview/OWNERS
\ No newline at end of file
diff --git a/components/js_injection/README.md b/components/js_injection/README.md
index b1e1f19..09e25e97 100644
--- a/components/js_injection/README.md
+++ b/components/js_injection/README.md
@@ -2,4 +2,8 @@
 inject javascript from the browser to the renderer, as well as a simple message
 port style API.
 
+As there is not an iOS builder blocking CQ, it is recommended to manually
+trigger an `ios-blink-rel-fyi` trybot job when making changes to detect
+breakages.
+
 [1] https://source.chromium.org/chromium/chromium/src/+/main:ios/web/content/js_messaging/
diff --git a/components/legion/BUILD.gn b/components/legion/BUILD.gn
index 4c498eb8..d4efbb4 100644
--- a/components/legion/BUILD.gn
+++ b/components/legion/BUILD.gn
@@ -23,12 +23,6 @@
     "attestation_handler_impl.h",
     "client.cc",
     "client.h",
-    "crypto/crypter.cc",
-    "crypto/crypter.h",
-    "crypto/noise.cc",
-    "crypto/noise.h",
-    "crypto/secure_session_impl.cc",
-    "crypto/secure_session_impl.h",
     "error_code.h",
     "features.cc",
     "features.h",
@@ -37,6 +31,8 @@
     "secure_channel_impl.cc",
     "secure_channel_impl.h",
     "secure_session.h",
+    "secure_session_async_impl.cc",
+    "secure_session_async_impl.h",
     "transport.h",
     "websocket_client.cc",
     "websocket_client.h",
@@ -45,6 +41,7 @@
     ":proto",
     ":proto_extras",
     "//base",
+    "//components/legion/crypto",
     "//crypto",
     "//mojo/public/cpp/system",
     "//net",
@@ -61,15 +58,17 @@
   sources = [
     "attestation_handler_impl_unittest.cc",
     "client_unittest.cc",
-    "crypto/crypter_unittest.cc",
-    "crypto/secure_session_impl_unittest.cc",
     "secure_channel_impl_unittest.cc",
+    "secure_session_async_impl_unittest.cc",
   ]
   deps = [
     ":legion",
     ":proto",
     "//base",
     "//base/test:test_support",
+    "//components/legion/crypto",
+    "//components/legion/crypto:test_support",
+    "//components/legion/crypto:unit_tests",
     "//testing/gmock",
     "//testing/gtest",
     "//third_party/oak:oak_proto",
diff --git a/components/legion/client.cc b/components/legion/client.cc
index 71e5bc9..d011c92d 100644
--- a/components/legion/client.cc
+++ b/components/legion/client.cc
@@ -17,10 +17,10 @@
 #include "base/task/task_runner.h"
 #include "base/time/time.h"
 #include "components/legion/attestation_handler_impl.h"
-#include "components/legion/crypto/secure_session_impl.h"
 #include "components/legion/features.h"
 #include "components/legion/proto/legion.pb.h"
 #include "components/legion/secure_channel_impl.h"
+#include "components/legion/secure_session_async_impl.h"
 #include "components/legion/websocket_client.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "url/gurl.h"
@@ -98,7 +98,7 @@
             base::BindRepeating(
                 [](network::mojom::NetworkContext* context) { return context; },
                 base::Unretained(context)));
-        auto secure_session = std::make_unique<SecureSessionImpl>();
+        auto secure_session = std::make_unique<SecureSessionAsyncImpl>();
         auto attestation_handler = std::make_unique<AttestationHandlerImpl>();
 
         return std::make_unique<SecureChannelImpl>(
diff --git a/components/legion/crypto/BUILD.gn b/components/legion/crypto/BUILD.gn
new file mode 100644
index 0000000..636c835
--- /dev/null
+++ b/components/legion/crypto/BUILD.gn
@@ -0,0 +1,50 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//components/proto_extras/proto_extras.gni")
+import("//testing/test.gni")
+import("//third_party/protobuf/proto_library.gni")
+
+source_set("crypto") {
+  sources = [
+    "constants.h",
+    "crypter.cc",
+    "crypter.h",
+    "noise.cc",
+    "noise.h",
+    "secure_session_impl.cc",
+    "secure_session_impl.h",
+  ]
+  deps = [
+    "//base",
+    "//crypto",
+    "//third_party/boringssl",
+  ]
+}
+
+source_set("test_support") {
+  sources = [
+    "test_server_secure_session.cc",
+    "test_server_secure_session.h",
+  ]
+  deps = [
+    ":crypto",
+    "//crypto",
+    "//third_party/boringssl",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "crypter_unittest.cc",
+    "secure_session_impl_unittest.cc",
+  ]
+  deps = [
+    ":crypto",
+    ":test_support",
+    "//base",
+    "//testing/gtest",
+  ]
+}
diff --git a/components/legion/crypto/constants.h b/components/legion/crypto/constants.h
new file mode 100644
index 0000000..879c628
--- /dev/null
+++ b/components/legion/crypto/constants.h
@@ -0,0 +1,17 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_LEGION_CRYPTO_CONSTANTS_H_
+#define COMPONENTS_LEGION_CRYPTO_CONSTANTS_H_
+
+#include <stdint.h>
+
+namespace legion {
+
+// Length of a P-256 public key in uncompressed X9.62 format.
+inline constexpr size_t kP256X962Length = 65;
+
+}  // namespace legion
+
+#endif  // COMPONENTS_LEGION_CRYPTO_CONSTANTS_H_
diff --git a/components/legion/crypto/noise.cc b/components/legion/crypto/noise.cc
index cdc7310..b08485f 100644
--- a/components/legion/crypto/noise.cc
+++ b/components/legion/crypto/noise.cc
@@ -8,6 +8,7 @@
 
 #include "base/check_op.h"
 #include "base/numerics/byte_conversions.h"
+#include "components/legion/crypto/constants.h"
 #include "crypto/aead.h"
 #include "crypto/hash.h"
 #include "crypto/kdf.h"
@@ -16,9 +17,6 @@
 
 namespace {
 
-// Length of a P-256 public key in uncompressed X9.62 format.
-constexpr size_t kP256X962Length = 65;
-
 // HKDF2 implements the functions with the same name from Noise[1],
 // specialized to the case where |num_outputs| is two.
 //
diff --git a/components/legion/crypto/secure_session_impl.cc b/components/legion/crypto/secure_session_impl.cc
index 4b423d9..4f0fbaa 100644
--- a/components/legion/crypto/secure_session_impl.cc
+++ b/components/legion/crypto/secure_session_impl.cc
@@ -9,62 +9,28 @@
 #include "base/check.h"
 #include "base/check_op.h"
 #include "base/functional/bind.h"
-#include "base/location.h"
 #include "base/logging.h"
-#include "base/task/sequenced_task_runner.h"
+#include "components/legion/crypto/constants.h"
 #include "third_party/boringssl/src/include/openssl/ecdh.h"
 #include "third_party/boringssl/src/include/openssl/nid.h"
-#include "third_party/oak/chromium/proto/session/session.pb.h"
 
 namespace legion {
 
-namespace {
-// Length of a P-256 public key in uncompressed X9.62 format.
-constexpr size_t kP256X962Length = 65;
-}  // namespace
+HandshakeMessage::HandshakeMessage(std::vector<uint8_t> ephemeral_public_key,
+                                   std::vector<uint8_t> ciphertext)
+    : ephemeral_public_key(ephemeral_public_key), ciphertext(ciphertext) {}
+
+HandshakeMessage::~HandshakeMessage() = default;
+
+HandshakeMessage::HandshakeMessage(HandshakeMessage&&) = default;
+
+HandshakeMessage& HandshakeMessage::operator=(HandshakeMessage&&) = default;
 
 SecureSessionImpl::SecureSessionImpl() = default;
 
 SecureSessionImpl::~SecureSessionImpl() = default;
 
-void SecureSessionImpl::GetHandshakeMessage(
-    SecureSession::GetHandshakeMessageOnceCallback callback) {
-  auto result = GetHandshakeMessageSync();
-
-  auto task_runner = base::SequencedTaskRunner::GetCurrentDefault();
-  task_runner->PostTask(FROM_HERE,
-                        base::BindOnce(std::move(callback), std::move(result)));
-}
-
-void SecureSessionImpl::ProcessHandshakeResponse(
-    const oak::session::v1::HandshakeResponse& response,
-    SecureSession::ProcessHandshakeResponseOnceCallback callback) {
-  bool result = ProcessHandshakeResponseSync(response);
-
-  auto task_runner = base::SequencedTaskRunner::GetCurrentDefault();
-  task_runner->PostTask(FROM_HERE, base::BindOnce(std::move(callback), result));
-}
-
-void SecureSessionImpl::Encrypt(const Request& data,
-                                EncryptOnceCallback callback) {
-  auto result = EncryptSync(data);
-
-  auto task_runner = base::SequencedTaskRunner::GetCurrentDefault();
-  task_runner->PostTask(FROM_HERE,
-                        base::BindOnce(std::move(callback), std::move(result)));
-}
-
-void SecureSessionImpl::Decrypt(const oak::session::v1::EncryptedMessage& data,
-                                DecryptOnceCallback callback) {
-  auto result = DecryptSync(data);
-
-  auto task_runner = base::SequencedTaskRunner::GetCurrentDefault();
-  task_runner->PostTask(FROM_HERE,
-                        base::BindOnce(std::move(callback), std::move(result)));
-}
-
-oak::session::v1::HandshakeRequest
-SecureSessionImpl::GetHandshakeMessageSync() {
+HandshakeMessage SecureSessionImpl::GetHandshakeMessage() {
   noise_.emplace();
   noise_->Init(Noise::HandshakeType::kNN);
   uint8_t prologue[1];
@@ -83,33 +49,29 @@
   noise_->MixHash(ephemeral_public_key_bytes);
   noise_->MixKey(ephemeral_public_key_bytes);
 
+  std::vector<uint8_t> ephemeral_public_key(
+      std::begin(ephemeral_public_key_bytes),
+      std::end(ephemeral_public_key_bytes));
   std::vector<uint8_t> ciphertext_request = noise_->EncryptAndHash({});
 
-  oak::session::v1::HandshakeRequest handshake_request;
-  auto* noise_message = handshake_request.mutable_noise_handshake_message();
-  noise_message->set_ephemeral_public_key(ephemeral_public_key_bytes,
-                                          sizeof(ephemeral_public_key_bytes));
-  noise_message->set_ciphertext(ciphertext_request.data(),
-                                ciphertext_request.size());
-  return handshake_request;
+  return HandshakeMessage(std::move(ephemeral_public_key),
+                          std::move(ciphertext_request));
 }
 
-bool SecureSessionImpl::ProcessHandshakeResponseSync(
-    const oak::session::v1::HandshakeResponse& response) {
+bool SecureSessionImpl::ProcessHandshakeResponse(
+    const HandshakeMessage& response) {
   if (!noise_) {
     DLOG(ERROR) << "Handshake not initiated.";
     return false;
   }
 
-  const auto& noise_response = response.noise_handshake_message();
-  std::vector<uint8_t> e(noise_response.ephemeral_public_key().begin(),
-                         noise_response.ephemeral_public_key().end());
-
   bssl::UniquePtr<EC_POINT> peer_point(
       EC_POINT_new(EC_KEY_get0_group(ephemeral_key_.get())));
   uint8_t shared_key_ee[32];
   const EC_GROUP* group = EC_KEY_get0_group(ephemeral_key_.get());
-  if (!EC_POINT_oct2point(group, peer_point.get(), e.data(), e.size(),
+  if (!EC_POINT_oct2point(group, peer_point.get(),
+                          response.ephemeral_public_key.data(),
+                          response.ephemeral_public_key.size(),
                           /*ctx=*/nullptr) ||
       ECDH_compute_key(shared_key_ee, sizeof(shared_key_ee), peer_point.get(),
                        ephemeral_key_.get(),
@@ -118,14 +80,11 @@
     return false;
   }
 
-  noise_->MixHash(e);
-  noise_->MixKey(e);
+  noise_->MixHash(response.ephemeral_public_key);
+  noise_->MixKey(response.ephemeral_public_key);
   noise_->MixKey(shared_key_ee);
 
-  std::vector<uint8_t> ciphertext_response(noise_response.ciphertext().begin(),
-                                           noise_response.ciphertext().end());
-
-  auto plaintext = noise_->DecryptAndHash(ciphertext_response);
+  auto plaintext = noise_->DecryptAndHash(response.ciphertext);
   if (!plaintext || !plaintext->empty()) {
     DLOG(ERROR) << "Invalid handshake message: " << plaintext.has_value();
     return false;
@@ -138,35 +97,34 @@
   return true;
 }
 
-std::optional<oak::session::v1::EncryptedMessage>
-SecureSessionImpl::EncryptSync(const Request& data) {
+std::optional<std::vector<uint8_t>> SecureSessionImpl::Encrypt(
+    const std::vector<uint8_t>& input) {
   if (!crypter_) {
     DLOG(ERROR) << "Crypter not initialized. Handshake must be completed.";
     return std::nullopt;
   }
 
-  auto encrypted_data = crypter_->Encrypt(data);
-  if (!encrypted_data) {
+  auto output = crypter_->Encrypt(input);
+  if (!output) {
     DLOG(ERROR) << "Encryption failed.";
     return std::nullopt;
   }
 
-  oak::session::v1::EncryptedMessage encrypted_message;
-  encrypted_message.set_ciphertext(encrypted_data->data(),
-                                   encrypted_data->size());
-  return encrypted_message;
+  return output;
 }
 
-std::optional<Response> SecureSessionImpl::DecryptSync(
-    const oak::session::v1::EncryptedMessage& data) {
+std::optional<std::vector<uint8_t>> SecureSessionImpl::Decrypt(
+    const std::vector<uint8_t>& input) {
   if (!crypter_) {
     DLOG(ERROR) << "Crypter not initialized. Handshake must be completed.";
     return std::nullopt;
   }
-  std::vector<uint8_t> encrypted_response(data.ciphertext().begin(),
-                                          data.ciphertext().end());
+  return crypter_->Decrypt(input);
+}
 
-  return crypter_->Decrypt(encrypted_response);
+void SecureSessionImpl::set_crypter_for_testing(
+    std::unique_ptr<Crypter> crypter) {
+  crypter_ = std::move(crypter);
 }
 
 }  // namespace legion
diff --git a/components/legion/crypto/secure_session_impl.h b/components/legion/crypto/secure_session_impl.h
index e9afada..69f79925 100644
--- a/components/legion/crypto/secure_session_impl.h
+++ b/components/legion/crypto/secure_session_impl.h
@@ -7,45 +7,47 @@
 
 #include <memory>
 #include <optional>
+#include <vector>
 
 #include "components/legion/crypto/crypter.h"
 #include "components/legion/crypto/noise.h"
-#include "components/legion/secure_session.h"
 #include "third_party/boringssl/src/include/openssl/ec.h"
 
 namespace legion {
 
-class SecureSessionImpl : public SecureSession {
+struct HandshakeMessage {
+  HandshakeMessage(std::vector<uint8_t> ephemeral_public_key,
+                   std::vector<uint8_t> ciphertext);
+  ~HandshakeMessage();
+
+  HandshakeMessage(HandshakeMessage&&);
+  HandshakeMessage& operator=(HandshakeMessage&&);
+
+  HandshakeMessage(const HandshakeMessage&) = delete;
+  HandshakeMessage& operator=(const HandshakeMessage&) = delete;
+
+  std::vector<uint8_t> ephemeral_public_key;
+  std::vector<uint8_t> ciphertext;
+};
+
+class SecureSessionImpl {
  public:
   SecureSessionImpl();
-  ~SecureSessionImpl() override;
+  ~SecureSessionImpl();
 
-  // SecureSession:
-  void GetHandshakeMessage(
-      SecureSession::GetHandshakeMessageOnceCallback callback) override;
-  void ProcessHandshakeResponse(
-      const oak::session::v1::HandshakeResponse& response,
-      ProcessHandshakeResponseOnceCallback callback) override;
-  void Encrypt(const Request& data, EncryptOnceCallback callback) override;
-  void Decrypt(const oak::session::v1::EncryptedMessage& data,
-               DecryptOnceCallback callback) override;
+  HandshakeMessage GetHandshakeMessage();
 
-  void set_crypter_for_testing(std::unique_ptr<Crypter> crypter) {
-    crypter_ = std::move(crypter);
-  }
+  bool ProcessHandshakeResponse(const HandshakeMessage& response);
+
+  std::optional<std::vector<uint8_t>> Encrypt(
+      const std::vector<uint8_t>& input);
+
+  std::optional<std::vector<uint8_t>> Decrypt(
+      const std::vector<uint8_t>& input);
+
+  void set_crypter_for_testing(std::unique_ptr<Crypter> crypter);
 
  private:
-  oak::session::v1::HandshakeRequest GetHandshakeMessageSync();
-
-  bool ProcessHandshakeResponseSync(
-      const oak::session::v1::HandshakeResponse& response);
-
-  std::optional<oak::session::v1::EncryptedMessage> EncryptSync(
-      const Request& data);
-
-  std::optional<Response> DecryptSync(
-      const oak::session::v1::EncryptedMessage& data);
-
   std::optional<Noise> noise_;
   bssl::UniquePtr<EC_KEY> ephemeral_key_;
   std::unique_ptr<Crypter> crypter_;
diff --git a/components/legion/crypto/secure_session_impl_unittest.cc b/components/legion/crypto/secure_session_impl_unittest.cc
index 0d12b655..2d4836b 100644
--- a/components/legion/crypto/secure_session_impl_unittest.cc
+++ b/components/legion/crypto/secure_session_impl_unittest.cc
@@ -4,199 +4,41 @@
 
 #include "components/legion/crypto/secure_session_impl.h"
 
-#include "base/test/task_environment.h"
-#include "base/test/test_future.h"
+#include "components/legion/crypto/constants.h"
+#include "components/legion/crypto/test_server_secure_session.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/boringssl/src/include/openssl/ecdh.h"
 #include "third_party/boringssl/src/include/openssl/nid.h"
-#include "third_party/oak/chromium/proto/session/session.pb.h"
 
 namespace legion {
 
 namespace {
 
-constexpr size_t kEphemeralPublicKeySize = 65;
-
-// Helper class to simulate the server-side of a secure session. This class
-// mirrors the functionality of `SecureSessionImpl` for the responder role in a
-// Noise handshake, and is used for end-to-end testing.
-class ServerSecureSession {
- public:
-  ServerSecureSession() {
-    // Initialize server Noise state for NN handshake.
-    noise_.Init(Noise::HandshakeType::kNN);
-    uint8_t prologue[1] = {0};
-    noise_.MixHash(prologue);
-  }
-
-  // Processes the client's opening handshake message, generates a response,
-  // and establishes session keys. A payload with a default empty value can be
-  // included in the response for testing invalid handshake scenarios.
-  std::optional<oak::session::v1::HandshakeResponse> ProcessHandshake(
-      const oak::session::v1::HandshakeRequest& client_handshake_request,
-      const std::vector<uint8_t>& payload = {}) {
-    bssl::UniquePtr<EC_KEY> server_e_key(
-        EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
-    const EC_GROUP* group = EC_KEY_get0_group(server_e_key.get());
-
-    bssl::UniquePtr<EC_POINT> client_e_point;
-    if (!ProcessClientRequest(client_handshake_request, group,
-                              &client_e_point)) {
-      return std::nullopt;
-    }
-
-    if (!EC_KEY_generate_key(server_e_key.get())) {
-      return std::nullopt;
-    }
-
-    return GenerateHandshakeResponse(server_e_key.get(), client_e_point.get(),
-                                     payload);
-  }
-
-  std::optional<Response> Decrypt(
-      const oak::session::v1::EncryptedMessage& request) {
-    if (!crypter_) {
-      return std::nullopt;
-    }
-    std::string ciphertext_str = request.ciphertext();
-    std::vector<uint8_t> ciphertext(ciphertext_str.begin(),
-                                    ciphertext_str.end());
-    return crypter_->Decrypt(ciphertext);
-  }
-
-  std::optional<oak::session::v1::EncryptedMessage> Encrypt(
-      const Request& plaintext) {
-    if (!crypter_) {
-      return std::nullopt;
-    }
-    auto ciphertext = crypter_->Encrypt(plaintext);
-    if (!ciphertext) {
-      return std::nullopt;
-    }
-    oak::session::v1::EncryptedMessage response;
-    response.set_ciphertext(ciphertext->data(), ciphertext->size());
-    return response;
-  }
-
- private:
-  // Processes the client's handshake request and performs the initial part of
-  // the Noise handshake protocol. Returns the client's ephemeral public key
-  // point on success.
-  bool ProcessClientRequest(const oak::session::v1::HandshakeRequest& request,
-                            const EC_GROUP* group,
-                            bssl::UniquePtr<EC_POINT>* out_client_e_point) {
-    const auto& client_noise_msg = request.noise_handshake_message();
-    std::vector<uint8_t> client_e_pub(
-        client_noise_msg.ephemeral_public_key().begin(),
-        client_noise_msg.ephemeral_public_key().end());
-    std::vector<uint8_t> client_ciphertext(
-        client_noise_msg.ciphertext().begin(),
-        client_noise_msg.ciphertext().end());
-
-    noise_.MixHash(client_e_pub);
-    noise_.MixKey(client_e_pub);
-
-    auto plaintext = noise_.DecryptAndHash(client_ciphertext);
-    if (!plaintext.has_value() || !plaintext->empty()) {
-      return false;
-    }
-
-    *out_client_e_point = bssl::UniquePtr<EC_POINT>(EC_POINT_new(group));
-    if (!EC_POINT_oct2point(group, out_client_e_point->get(),
-                            client_e_pub.data(), client_e_pub.size(),
-                            nullptr)) {
-      return false;
-    }
-    return true;
-  }
-
-  // Completes the handshake, generates the server's handshake response, and
-  // establishes session keys.
-  std::optional<oak::session::v1::HandshakeResponse> GenerateHandshakeResponse(
-      EC_KEY* server_e_key,
-      const EC_POINT* client_e_point,
-      const std::vector<uint8_t>& payload) {
-    const EC_GROUP* group = EC_KEY_get0_group(server_e_key);
-
-    uint8_t server_e_pub_bytes[kEphemeralPublicKeySize] = {0};
-    if (sizeof(server_e_pub_bytes) !=
-        EC_POINT_point2oct(group, EC_KEY_get0_public_key(server_e_key),
-                           POINT_CONVERSION_UNCOMPRESSED, server_e_pub_bytes,
-                           sizeof(server_e_pub_bytes), nullptr)) {
-      return std::nullopt;
-    }
-
-    noise_.MixHash(server_e_pub_bytes);
-    noise_.MixKey(server_e_pub_bytes);
-
-    uint8_t shared_key_ee[32] = {0};
-    if (sizeof(shared_key_ee) !=
-        ECDH_compute_key(shared_key_ee, sizeof(shared_key_ee), client_e_point,
-                         server_e_key, nullptr)) {
-      return std::nullopt;
-    }
-    noise_.MixKey(shared_key_ee);
-
-    std::vector<uint8_t> server_ciphertext = noise_.EncryptAndHash(payload);
-
-    auto [server_read_key, server_write_key] = noise_.traffic_keys();
-    crypter_ = std::make_unique<Crypter>(server_read_key, server_write_key);
-
-    oak::session::v1::HandshakeResponse server_handshake_response;
-    auto* server_noise_msg =
-        server_handshake_response.mutable_noise_handshake_message();
-    server_noise_msg->set_ephemeral_public_key(server_e_pub_bytes,
-                                               sizeof(server_e_pub_bytes));
-    server_noise_msg->set_ciphertext(server_ciphertext.data(),
-                                     server_ciphertext.size());
-    return server_handshake_response;
-  }
-
-  Noise noise_;
-  std::unique_ptr<Crypter> crypter_;
-};
-
 class SecureSessionImplTest : public ::testing::Test {
  protected:
-  void PerformValidHandshake(ServerSecureSession& server_session) {
-    auto client_handshake_request = [&]() {
-      base::test::TestFuture<oak::session::v1::HandshakeRequest> future;
-      client_session_.GetHandshakeMessage(future.GetCallback());
-      return future.Get();
-    }();
+  void PerformValidHandshake(TestServerSecureSession& server_session) {
+    auto client_handshake_request = client_session_.GetHandshakeMessage();
 
     auto server_handshake_response =
         server_session.ProcessHandshake(client_handshake_request);
     ASSERT_TRUE(server_handshake_response.has_value());
 
-    {
-      base::test::TestFuture<bool> future;
-      client_session_.ProcessHandshakeResponse(
-          server_handshake_response.value(), future.GetCallback());
-      ASSERT_TRUE(future.Get());
-    }
+    ASSERT_TRUE(client_session_.ProcessHandshakeResponse(
+        server_handshake_response.value()));
   }
 
   SecureSessionImpl client_session_;
-
- private:
-  base::test::TaskEnvironment task_environment_;
 };
 
 // End-to-end test of the handshake and encryption/decryption in both
 // directions.
 TEST_F(SecureSessionImplTest, HandshakeAndEncryptDecryptSucceeds) {
-  ServerSecureSession server_session;
+  TestServerSecureSession server_session;
   PerformValidHandshake(server_session);
 
   // Test encryption and decryption from client to server.
-  const Request client_plaintext = {1, 2, 3};
-  auto encrypted_from_client = [&]() {
-    base::test::TestFuture<std::optional<oak::session::v1::EncryptedMessage>>
-        future;
-    client_session_.Encrypt(client_plaintext, future.GetCallback());
-    return future.Get();
-  }();
+  const std::vector<uint8_t> client_plaintext = {1, 2, 3};
+  auto encrypted_from_client = client_session_.Encrypt(client_plaintext);
   ASSERT_TRUE(encrypted_from_client.has_value());
 
   auto decrypted_by_server =
@@ -205,124 +47,85 @@
   EXPECT_EQ(client_plaintext, decrypted_by_server.value());
 
   // Test encryption and decryption from server to client.
-  const Request server_plaintext = {4, 5, 6};
+  const std::vector<uint8_t> server_plaintext = {4, 5, 6};
   auto encrypted_from_server = server_session.Encrypt(server_plaintext);
   ASSERT_TRUE(encrypted_from_server.has_value());
 
-  {
-    base::test::TestFuture<std::optional<Response>> future;
-    client_session_.Decrypt(encrypted_from_server.value(),
-                            future.GetCallback());
-    auto decrypted_by_client = future.Get();
-
-    ASSERT_TRUE(decrypted_by_client.has_value());
-    EXPECT_EQ(server_plaintext, decrypted_by_client.value());
-  }
+  auto decrypted_by_client =
+      client_session_.Decrypt(encrypted_from_server.value());
+  ASSERT_TRUE(decrypted_by_client.has_value());
+  EXPECT_EQ(server_plaintext, decrypted_by_client.value());
 }
 
 TEST_F(SecureSessionImplTest, GetHandshakeMessageSucceeds) {
-  base::test::TestFuture<oak::session::v1::HandshakeRequest> future;
-  client_session_.GetHandshakeMessage(future.GetCallback());
-  auto request = future.Get();
-
-  EXPECT_TRUE(request.has_noise_handshake_message());
-
-  const auto& noise_msg = request.noise_handshake_message();
-  EXPECT_EQ(noise_msg.ephemeral_public_key().size(), kEphemeralPublicKeySize);
-  EXPECT_FALSE(noise_msg.ciphertext().empty());
+  auto request = client_session_.GetHandshakeMessage();
+  EXPECT_EQ(request.ephemeral_public_key.size(), kP256X962Length);
+  EXPECT_FALSE(request.ciphertext.empty());
 }
 
 TEST_F(SecureSessionImplTest, ProcessHandshakeResponseInvalidPeerKey) {
   // Though the result is not used, it's important to call GetHandshakeMessage()
   // before ProcessHandshakeResponse().
-  {
-    base::test::TestFuture<oak::session::v1::HandshakeRequest> future;
-    client_session_.GetHandshakeMessage(future.GetCallback());
-    ASSERT_TRUE(future.Wait());
-  }
+  client_session_.GetHandshakeMessage();
 
-  oak::session::v1::HandshakeResponse response;
-  auto* noise_msg = response.mutable_noise_handshake_message();
+  constexpr char kInvalidKey[] = "invalid key";
+  constexpr char kCiphertext[] = "some ciphertext";
+
   // Malform the key by providing an incorrect size.
-  noise_msg->set_ephemeral_public_key("invalid key", 11);
-  noise_msg->set_ciphertext("some ciphertext");
+  HandshakeMessage response(
+      std::vector<uint8_t>(std::begin(kInvalidKey), std::end(kInvalidKey)),
+      std::vector<uint8_t>(std::begin(kCiphertext), std::end(kCiphertext)));
 
-  {
-    base::test::TestFuture<bool> future;
-    client_session_.ProcessHandshakeResponse(response, future.GetCallback());
-    EXPECT_FALSE(future.Get());
-  }
+  EXPECT_FALSE(client_session_.ProcessHandshakeResponse(response));
 }
 
 TEST_F(SecureSessionImplTest, ProcessHandshakeResponseInvalidCiphertext) {
   // Though the result is not used, it's important to call GetHandshakeMessage()
   // before ProcessHandshakeResponse().
-  {
-    base::test::TestFuture<oak::session::v1::HandshakeRequest> future;
-    client_session_.GetHandshakeMessage(future.GetCallback());
-    ASSERT_TRUE(future.Wait());
-  }
+  client_session_.GetHandshakeMessage();
+
+  uint8_t server_e_pub_bytes[kP256X962Length] = {0};  // Test key
+
+  constexpr char kCorruptedCiphertext[] = "corrupted ciphertext";
 
   // Create a valid server response, but then corrupt the ciphertext.
-  oak::session::v1::HandshakeResponse server_handshake_response;
-  auto* server_noise_msg =
-      server_handshake_response.mutable_noise_handshake_message();
+  HandshakeMessage server_handshake_response(
+      std::vector(std::begin(server_e_pub_bytes), std::end(server_e_pub_bytes)),
+      std::vector<uint8_t>(std::begin(kCorruptedCiphertext),
+                           std::end(kCorruptedCiphertext)));
 
-  uint8_t server_e_pub_bytes[kEphemeralPublicKeySize] = {0};  // Test key
-  server_noise_msg->set_ephemeral_public_key(server_e_pub_bytes,
-                                             sizeof(server_e_pub_bytes));
-  server_noise_msg->set_ciphertext("corrupted ciphertext");
-
-  {
-    base::test::TestFuture<bool> future;
-    client_session_.ProcessHandshakeResponse(server_handshake_response,
-                                             future.GetCallback());
-    EXPECT_FALSE(future.Get());
-  }
+  EXPECT_FALSE(
+      client_session_.ProcessHandshakeResponse(server_handshake_response));
 }
 
 TEST_F(SecureSessionImplTest, EncryptBeforeHandshake) {
-  const Request client_plaintext = {1, 2, 3};
+  const std::vector<uint8_t> client_plaintext = {1, 2, 3};
 
-  base::test::TestFuture<std::optional<oak::session::v1::EncryptedMessage>>
-      future;
-  client_session_.Encrypt(client_plaintext, future.GetCallback());
-  auto encrypted = future.Get();
-
+  auto encrypted = client_session_.Encrypt(client_plaintext);
   EXPECT_FALSE(encrypted.has_value());
 }
 
 TEST_F(SecureSessionImplTest, DecryptBeforeHandshake) {
-  oak::session::v1::EncryptedMessage encrypted_message;
-  encrypted_message.set_ciphertext("some data");
+  const std::vector<uint8_t> response = {1, 2, 3};
 
-  base::test::TestFuture<std::optional<Response>> future;
-  client_session_.Decrypt(encrypted_message, future.GetCallback());
-  auto decrypted = future.Get();
-
+  auto decrypted = client_session_.Decrypt(response);
   EXPECT_FALSE(decrypted.has_value());
 }
 
 // Tests that ProcessHandshakeResponse fails if called before
 // GetHandshakeMessage.
 TEST_F(SecureSessionImplTest, ProcessHandshakeResponseWithoutHandshake) {
-  oak::session::v1::HandshakeResponse response;
+  HandshakeMessage response({}, {});
 
-  base::test::TestFuture<bool> future;
-  client_session_.ProcessHandshakeResponse(response, future.GetCallback());
-  EXPECT_FALSE(future.Get());
+  EXPECT_FALSE(client_session_.ProcessHandshakeResponse(response));
 }
 
 // Tests that the handshake fails if the server's response includes a payload,
 // which is not allowed in the NN handshake pattern.
 TEST_F(SecureSessionImplTest, ProcessHandshakeResponseNonEmptyPlaintext) {
-  auto client_handshake_request = [&]() {
-    base::test::TestFuture<oak::session::v1::HandshakeRequest> future;
-    client_session_.GetHandshakeMessage(future.GetCallback());
-    return future.Get();
-  }();
+  auto client_handshake_request = client_session_.GetHandshakeMessage();
 
-  ServerSecureSession server_session;
+  TestServerSecureSession server_session;
   // Generate a server response with a non-empty payload, which is invalid for
   // the NN handshake pattern.
   auto server_handshake_response =
@@ -331,12 +134,8 @@
 
   // The client should reject the response because the decrypted payload is not
   // empty.
-  {
-    base::test::TestFuture<bool> future;
-    client_session_.ProcessHandshakeResponse(server_handshake_response.value(),
-                                             future.GetCallback());
-    EXPECT_FALSE(future.Get());
-  }
+  EXPECT_FALSE(client_session_.ProcessHandshakeResponse(
+      server_handshake_response.value()));
 }
 
 }  // namespace
diff --git a/components/legion/crypto/test_server_secure_session.cc b/components/legion/crypto/test_server_secure_session.cc
new file mode 100644
index 0000000..1625335
--- /dev/null
+++ b/components/legion/crypto/test_server_secure_session.cc
@@ -0,0 +1,124 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/legion/crypto/test_server_secure_session.h"
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include "components/legion/crypto/constants.h"
+#include "components/legion/crypto/crypter.h"
+#include "components/legion/crypto/noise.h"
+#include "components/legion/crypto/secure_session_impl.h"
+#include "third_party/boringssl/src/include/openssl/ecdh.h"
+#include "third_party/boringssl/src/include/openssl/nid.h"
+
+namespace legion {
+
+TestServerSecureSession::TestServerSecureSession() {
+  // Initialize server Noise state for NN handshake.
+  noise_.Init(Noise::HandshakeType::kNN);
+  uint8_t prologue[1] = {0};
+  noise_.MixHash(prologue);
+}
+
+TestServerSecureSession::~TestServerSecureSession() = default;
+
+std::optional<HandshakeMessage> TestServerSecureSession::ProcessHandshake(
+    const HandshakeMessage& client_handshake_request,
+    const std::vector<uint8_t>& payload) {
+  bssl::UniquePtr<EC_KEY> server_e_key(
+      EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+  const EC_GROUP* group = EC_KEY_get0_group(server_e_key.get());
+
+  bssl::UniquePtr<EC_POINT> client_e_point;
+  if (!ProcessClientRequest(client_handshake_request, group, &client_e_point)) {
+    return std::nullopt;
+  }
+
+  if (!EC_KEY_generate_key(server_e_key.get())) {
+    return std::nullopt;
+  }
+
+  return GenerateHandshakeResponse(server_e_key.get(), client_e_point.get(),
+                                   payload);
+}
+
+std::optional<std::vector<uint8_t>> TestServerSecureSession::Decrypt(
+    const std::vector<uint8_t>& input) {
+  if (!crypter_) {
+    return std::nullopt;
+  }
+  return crypter_->Decrypt(input);
+}
+
+std::optional<std::vector<uint8_t>> TestServerSecureSession::Encrypt(
+    const std::vector<uint8_t>& input) {
+  if (!crypter_) {
+    return std::nullopt;
+  }
+  return crypter_->Encrypt(input);
+}
+
+bool TestServerSecureSession::ProcessClientRequest(
+    const HandshakeMessage& request,
+    const EC_GROUP* group,
+    bssl::UniquePtr<EC_POINT>* out_client_e_point) {
+  noise_.MixHash(request.ephemeral_public_key);
+  noise_.MixKey(request.ephemeral_public_key);
+
+  auto decoded_msg = noise_.DecryptAndHash(request.ciphertext);
+  if (!decoded_msg.has_value() || !decoded_msg->empty()) {
+    return false;
+  }
+
+  *out_client_e_point = bssl::UniquePtr<EC_POINT>(EC_POINT_new(group));
+  if (!EC_POINT_oct2point(group, out_client_e_point->get(),
+                          request.ephemeral_public_key.data(),
+                          request.ephemeral_public_key.size(), nullptr)) {
+    return false;
+  }
+  return true;
+}
+
+std::optional<HandshakeMessage>
+TestServerSecureSession::GenerateHandshakeResponse(
+    EC_KEY* server_e_key,
+    const EC_POINT* client_e_point,
+    const std::vector<uint8_t>& payload) {
+  const EC_GROUP* group = EC_KEY_get0_group(server_e_key);
+
+  uint8_t server_e_pub_bytes[kP256X962Length] = {0};
+  if (sizeof(server_e_pub_bytes) !=
+      EC_POINT_point2oct(group, EC_KEY_get0_public_key(server_e_key),
+                         POINT_CONVERSION_UNCOMPRESSED, server_e_pub_bytes,
+                         sizeof(server_e_pub_bytes), nullptr)) {
+    return std::nullopt;
+  }
+
+  noise_.MixHash(server_e_pub_bytes);
+  noise_.MixKey(server_e_pub_bytes);
+
+  uint8_t shared_key_ee[32] = {0};
+  if (sizeof(shared_key_ee) !=
+      ECDH_compute_key(shared_key_ee, sizeof(shared_key_ee), client_e_point,
+                       server_e_key, nullptr)) {
+    return std::nullopt;
+  }
+  noise_.MixKey(shared_key_ee);
+
+  std::vector<uint8_t> server_ciphertext = noise_.EncryptAndHash(payload);
+
+  auto [server_read_key, server_write_key] = noise_.traffic_keys();
+  crypter_ = std::make_unique<Crypter>(server_read_key, server_write_key);
+
+  std::vector<uint8_t> ephemeral_public_key(std::begin(server_e_pub_bytes),
+                                            std::end(server_e_pub_bytes));
+
+  return HandshakeMessage(std::move(ephemeral_public_key),
+                          std::move(server_ciphertext));
+}
+
+}  // namespace legion
diff --git a/components/legion/crypto/test_server_secure_session.h b/components/legion/crypto/test_server_secure_session.h
new file mode 100644
index 0000000..3568b78
--- /dev/null
+++ b/components/legion/crypto/test_server_secure_session.h
@@ -0,0 +1,62 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_LEGION_CRYPTO_TEST_SERVER_SECURE_SESSION_H_
+#define COMPONENTS_LEGION_CRYPTO_TEST_SERVER_SECURE_SESSION_H_
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include "components/legion/crypto/crypter.h"
+#include "components/legion/crypto/noise.h"
+#include "components/legion/crypto/secure_session_impl.h"
+#include "third_party/boringssl/src/include/openssl/ecdh.h"
+#include "third_party/boringssl/src/include/openssl/nid.h"
+
+namespace legion {
+
+// Helper class to simulate the server-side of a secure session. This class
+// mirrors the functionality of `SecureSessionImpl` for the responder role in a
+// Noise handshake, and is used for end-to-end testing.
+class TestServerSecureSession {
+ public:
+  TestServerSecureSession();
+  ~TestServerSecureSession();
+
+  // Processes the client's opening handshake message, generates a response,
+  // and establishes session keys. A payload with a default empty value can be
+  // included in the response for testing invalid handshake scenarios.
+  std::optional<HandshakeMessage> ProcessHandshake(
+      const HandshakeMessage& client_handshake_request,
+      const std::vector<uint8_t>& payload = {});
+
+  std::optional<std::vector<uint8_t>> Decrypt(
+      const std::vector<uint8_t>& input);
+
+  std::optional<std::vector<uint8_t>> Encrypt(
+      const std::vector<uint8_t>& input);
+
+ private:
+  // Processes the client's handshake request and performs the initial part of
+  // the Noise handshake protocol. Returns the client's ephemeral public key
+  // point on success.
+  bool ProcessClientRequest(const HandshakeMessage& request,
+                            const EC_GROUP* group,
+                            bssl::UniquePtr<EC_POINT>* out_client_e_point);
+
+  // Completes the handshake, generates the server's handshake response, and
+  // establishes session keys.
+  std::optional<HandshakeMessage> GenerateHandshakeResponse(
+      EC_KEY* server_e_key,
+      const EC_POINT* client_e_point,
+      const std::vector<uint8_t>& payload);
+
+  Noise noise_;
+  std::unique_ptr<Crypter> crypter_;
+};
+
+}  // namespace legion
+
+#endif  // COMPONENTS_LEGION_CRYPTO_TEST_SERVER_SECURE_SESSION_H_
diff --git a/components/legion/secure_session_async_impl.cc b/components/legion/secure_session_async_impl.cc
new file mode 100644
index 0000000..6af9dea
--- /dev/null
+++ b/components/legion/secure_session_async_impl.cc
@@ -0,0 +1,113 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/legion/secure_session_async_impl.h"
+
+#include <utility>
+
+#include "base/functional/bind.h"
+#include "base/location.h"
+#include "base/task/sequenced_task_runner.h"
+#include "crypto/secure_session_impl.h"
+#include "third_party/oak/chromium/proto/session/session.pb.h"
+
+namespace legion {
+
+namespace {
+
+oak::session::v1::HandshakeRequest ConvertToRequestProto(
+    const HandshakeMessage& input) {
+  oak::session::v1::HandshakeRequest output;
+  output.mutable_noise_handshake_message()->set_ephemeral_public_key(
+      input.ephemeral_public_key.data(), input.ephemeral_public_key.size());
+  output.mutable_noise_handshake_message()->set_ciphertext(
+      input.ciphertext.data(), input.ciphertext.size());
+  return output;
+}
+
+std::optional<HandshakeMessage> ConvertToHandshakeMessage(
+    const oak::session::v1::HandshakeResponse& response) {
+  if (!response.has_noise_handshake_message()) {
+    return std::nullopt;
+  }
+
+  const auto noise_msg = response.noise_handshake_message();
+
+  HandshakeMessage output(
+      std::vector<uint8_t>(noise_msg.ephemeral_public_key().begin(),
+                           noise_msg.ephemeral_public_key().end()),
+      std::vector<uint8_t>(noise_msg.ciphertext().begin(),
+                           noise_msg.ciphertext().end()));
+  return output;
+}
+
+std::optional<oak::session::v1::EncryptedMessage> ConvertToEncryptedMessage(
+    const std::optional<std::vector<uint8_t>>& encrypted_data) {
+  if (!encrypted_data.has_value()) {
+    return std::nullopt;
+  }
+
+  oak::session::v1::EncryptedMessage encrypted_message;
+  encrypted_message.set_ciphertext(encrypted_data.value().data(),
+                                   encrypted_data.value().size());
+  return encrypted_message;
+}
+
+std::vector<uint8_t> ConvertToBytes(
+    const oak::session::v1::EncryptedMessage& encrypted_msg) {
+  return std::vector<uint8_t>(encrypted_msg.ciphertext().begin(),
+                              encrypted_msg.ciphertext().end());
+}
+
+}  // namespace
+
+SecureSessionAsyncImpl::SecureSessionAsyncImpl() = default;
+
+SecureSessionAsyncImpl::~SecureSessionAsyncImpl() = default;
+
+void SecureSessionAsyncImpl::GetHandshakeMessage(
+    SecureSession::GetHandshakeMessageOnceCallback callback) {
+  auto result = ConvertToRequestProto(sync_impl_.GetHandshakeMessage());
+
+  auto task_runner = base::SequencedTaskRunner::GetCurrentDefault();
+  task_runner->PostTask(FROM_HERE, base::BindOnce(std::move(callback), result));
+}
+
+void SecureSessionAsyncImpl::ProcessHandshakeResponse(
+    const oak::session::v1::HandshakeResponse& response,
+    SecureSession::ProcessHandshakeResponseOnceCallback callback) {
+  auto handshake_msg = ConvertToHandshakeMessage(response);
+
+  bool result = handshake_msg.has_value() &&
+                sync_impl_.ProcessHandshakeResponse(handshake_msg.value());
+
+  auto task_runner = base::SequencedTaskRunner::GetCurrentDefault();
+  task_runner->PostTask(FROM_HERE, base::BindOnce(std::move(callback), result));
+}
+
+void SecureSessionAsyncImpl::Encrypt(const Request& data,
+                                     EncryptOnceCallback callback) {
+  auto result = ConvertToEncryptedMessage(sync_impl_.Encrypt(data));
+
+  auto task_runner = base::SequencedTaskRunner::GetCurrentDefault();
+  task_runner->PostTask(FROM_HERE,
+                        base::BindOnce(std::move(callback), std::move(result)));
+}
+
+void SecureSessionAsyncImpl::Decrypt(
+    const oak::session::v1::EncryptedMessage& data,
+    DecryptOnceCallback callback) {
+  auto result = sync_impl_.Decrypt(ConvertToBytes(data));
+
+  auto task_runner = base::SequencedTaskRunner::GetCurrentDefault();
+  task_runner->PostTask(FROM_HERE,
+                        base::BindOnce(std::move(callback), std::move(result)));
+}
+
+void SecureSessionAsyncImpl::set_crypter_for_testing(
+    std::unique_ptr<Crypter> crypter) {
+  sync_impl_.set_crypter_for_testing(std::move(crypter));
+}
+
+}  // namespace legion
diff --git a/components/legion/secure_session_async_impl.h b/components/legion/secure_session_async_impl.h
new file mode 100644
index 0000000..b8f3d8c
--- /dev/null
+++ b/components/legion/secure_session_async_impl.h
@@ -0,0 +1,40 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_LEGION_SECURE_SESSION_ASYNC_IMPL_H_
+#define COMPONENTS_LEGION_SECURE_SESSION_ASYNC_IMPL_H_
+
+#include <memory>
+#include <optional>
+
+#include "components/legion/crypto/secure_session_impl.h"
+#include "components/legion/secure_session.h"
+#include "third_party/oak/chromium/proto/session/session.pb.h"
+
+namespace legion {
+
+class SecureSessionAsyncImpl : public SecureSession {
+ public:
+  SecureSessionAsyncImpl();
+  ~SecureSessionAsyncImpl() override;
+
+  // SecureSession:
+  void GetHandshakeMessage(
+      SecureSession::GetHandshakeMessageOnceCallback callback) override;
+  void ProcessHandshakeResponse(
+      const oak::session::v1::HandshakeResponse& response,
+      ProcessHandshakeResponseOnceCallback callback) override;
+  void Encrypt(const Request& data, EncryptOnceCallback callback) override;
+  void Decrypt(const oak::session::v1::EncryptedMessage& data,
+               DecryptOnceCallback callback) override;
+
+  void set_crypter_for_testing(std::unique_ptr<Crypter> crypter);
+
+ private:
+  SecureSessionImpl sync_impl_;
+};
+
+}  // namespace legion
+
+#endif  // COMPONENTS_LEGION_SECURE_SESSION_ASYNC_IMPL_H_
diff --git a/components/legion/secure_session_async_impl_unittest.cc b/components/legion/secure_session_async_impl_unittest.cc
new file mode 100644
index 0000000..8e08763
--- /dev/null
+++ b/components/legion/secure_session_async_impl_unittest.cc
@@ -0,0 +1,249 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/legion/secure_session_async_impl.h"
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "components/legion/crypto/constants.h"
+#include "components/legion/crypto/test_server_secure_session.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/boringssl/src/include/openssl/ecdh.h"
+#include "third_party/boringssl/src/include/openssl/nid.h"
+#include "third_party/oak/chromium/proto/session/session.pb.h"
+
+namespace legion {
+
+namespace {
+
+HandshakeMessage ConvertToHandshakeMessage(
+    const oak::session::v1::HandshakeRequest& response) {
+  CHECK(response.has_noise_handshake_message());
+
+  const auto noise_msg = response.noise_handshake_message();
+  HandshakeMessage output(
+      std::vector<uint8_t>(noise_msg.ephemeral_public_key().begin(),
+                           noise_msg.ephemeral_public_key().end()),
+      std::vector<uint8_t>(noise_msg.ciphertext().begin(),
+                           noise_msg.ciphertext().end()));
+  return output;
+}
+
+oak::session::v1::HandshakeResponse ConvertToResponseProto(
+    const HandshakeMessage& input) {
+  oak::session::v1::HandshakeResponse output;
+  output.mutable_noise_handshake_message()->set_ephemeral_public_key(
+      input.ephemeral_public_key.data(), input.ephemeral_public_key.size());
+  output.mutable_noise_handshake_message()->set_ciphertext(
+      input.ciphertext.data(), input.ciphertext.size());
+  return output;
+}
+
+oak::session::v1::EncryptedMessage ConvertToEncryptedMessage(
+    const std::vector<uint8_t>& encrypted_data) {
+  oak::session::v1::EncryptedMessage encrypted_message;
+  encrypted_message.set_ciphertext(encrypted_data.data(),
+                                   encrypted_data.size());
+  return encrypted_message;
+}
+
+std::vector<uint8_t> ConvertToBytes(
+    const oak::session::v1::EncryptedMessage& encrypted_msg) {
+  return std::vector<uint8_t>(encrypted_msg.ciphertext().begin(),
+                              encrypted_msg.ciphertext().end());
+}
+
+class SecureSessionAsyncImplTest : public ::testing::Test {
+ protected:
+  void PerformValidHandshake(TestServerSecureSession& server_session) {
+    auto client_handshake_request = [&]() {
+      base::test::TestFuture<oak::session::v1::HandshakeRequest> future;
+      client_session_.GetHandshakeMessage(future.GetCallback());
+      return future.Get();
+    }();
+
+    auto server_handshake_response = server_session.ProcessHandshake(
+        ConvertToHandshakeMessage(client_handshake_request));
+    ASSERT_TRUE(server_handshake_response.has_value());
+
+    {
+      base::test::TestFuture<bool> future;
+      client_session_.ProcessHandshakeResponse(
+          ConvertToResponseProto(server_handshake_response.value()),
+          future.GetCallback());
+      ASSERT_TRUE(future.Get());
+    }
+  }
+
+  SecureSessionAsyncImpl client_session_;
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+};
+
+// End-to-end test of the handshake and encryption/decryption in both
+// directions.
+TEST_F(SecureSessionAsyncImplTest, HandshakeAndEncryptDecryptSucceeds) {
+  TestServerSecureSession server_session;
+  PerformValidHandshake(server_session);
+
+  // Test encryption and decryption from client to server.
+  const Request client_plaintext = {1, 2, 3};
+  auto encrypted_from_client = [&]() {
+    base::test::TestFuture<std::optional<oak::session::v1::EncryptedMessage>>
+        future;
+    client_session_.Encrypt(client_plaintext, future.GetCallback());
+    return future.Get();
+  }();
+  ASSERT_TRUE(encrypted_from_client.has_value());
+
+  auto decrypted_by_server =
+      server_session.Decrypt(ConvertToBytes(encrypted_from_client.value()));
+  ASSERT_TRUE(decrypted_by_server.has_value());
+  EXPECT_EQ(client_plaintext, decrypted_by_server.value());
+
+  // Test encryption and decryption from server to client.
+  const Request server_plaintext = {4, 5, 6};
+  auto encrypted_from_server = server_session.Encrypt(server_plaintext);
+  ASSERT_TRUE(encrypted_from_server.has_value());
+
+  {
+    base::test::TestFuture<std::optional<Response>> future;
+    client_session_.Decrypt(
+        ConvertToEncryptedMessage(encrypted_from_server.value()),
+        future.GetCallback());
+    auto decrypted_by_client = future.Get();
+
+    ASSERT_TRUE(decrypted_by_client.has_value());
+    EXPECT_EQ(server_plaintext, decrypted_by_client.value());
+  }
+}
+
+TEST_F(SecureSessionAsyncImplTest, GetHandshakeMessageSucceeds) {
+  base::test::TestFuture<oak::session::v1::HandshakeRequest> future;
+  client_session_.GetHandshakeMessage(future.GetCallback());
+  auto request = future.Get();
+
+  EXPECT_TRUE(request.has_noise_handshake_message());
+
+  const auto& noise_msg = request.noise_handshake_message();
+  EXPECT_EQ(noise_msg.ephemeral_public_key().size(), kP256X962Length);
+  EXPECT_FALSE(noise_msg.ciphertext().empty());
+}
+
+TEST_F(SecureSessionAsyncImplTest, ProcessHandshakeResponseInvalidPeerKey) {
+  // Though the result is not used, it's important to call GetHandshakeMessage()
+  // before ProcessHandshakeResponse().
+  {
+    base::test::TestFuture<oak::session::v1::HandshakeRequest> future;
+    client_session_.GetHandshakeMessage(future.GetCallback());
+    ASSERT_TRUE(future.Wait());
+  }
+
+  oak::session::v1::HandshakeResponse response;
+  auto* noise_msg = response.mutable_noise_handshake_message();
+  // Malform the key by providing an incorrect size.
+  noise_msg->set_ephemeral_public_key("invalid key", 11);
+  noise_msg->set_ciphertext("some ciphertext");
+
+  {
+    base::test::TestFuture<bool> future;
+    client_session_.ProcessHandshakeResponse(response, future.GetCallback());
+    EXPECT_FALSE(future.Get());
+  }
+}
+
+TEST_F(SecureSessionAsyncImplTest, ProcessHandshakeResponseInvalidCiphertext) {
+  // Though the result is not used, it's important to call GetHandshakeMessage()
+  // before ProcessHandshakeResponse().
+  {
+    base::test::TestFuture<oak::session::v1::HandshakeRequest> future;
+    client_session_.GetHandshakeMessage(future.GetCallback());
+    ASSERT_TRUE(future.Wait());
+  }
+
+  // Create a valid server response, but then corrupt the ciphertext.
+  oak::session::v1::HandshakeResponse server_handshake_response;
+  auto* server_noise_msg =
+      server_handshake_response.mutable_noise_handshake_message();
+
+  uint8_t server_e_pub_bytes[kP256X962Length] = {0};  // Test key
+  server_noise_msg->set_ephemeral_public_key(server_e_pub_bytes,
+                                             sizeof(server_e_pub_bytes));
+  server_noise_msg->set_ciphertext("corrupted ciphertext");
+
+  {
+    base::test::TestFuture<bool> future;
+    client_session_.ProcessHandshakeResponse(server_handshake_response,
+                                             future.GetCallback());
+    EXPECT_FALSE(future.Get());
+  }
+}
+
+TEST_F(SecureSessionAsyncImplTest, EncryptBeforeHandshake) {
+  const Request client_plaintext = {1, 2, 3};
+
+  base::test::TestFuture<std::optional<oak::session::v1::EncryptedMessage>>
+      future;
+  client_session_.Encrypt(client_plaintext, future.GetCallback());
+  auto encrypted = future.Get();
+
+  EXPECT_FALSE(encrypted.has_value());
+}
+
+TEST_F(SecureSessionAsyncImplTest, DecryptBeforeHandshake) {
+  oak::session::v1::EncryptedMessage encrypted_message;
+  encrypted_message.set_ciphertext("some data");
+
+  base::test::TestFuture<std::optional<Response>> future;
+  client_session_.Decrypt(encrypted_message, future.GetCallback());
+  auto decrypted = future.Get();
+
+  EXPECT_FALSE(decrypted.has_value());
+}
+
+// Tests that ProcessHandshakeResponse fails if called before
+// GetHandshakeMessage.
+TEST_F(SecureSessionAsyncImplTest, ProcessHandshakeResponseWithoutHandshake) {
+  oak::session::v1::HandshakeResponse response;
+
+  base::test::TestFuture<bool> future;
+  client_session_.ProcessHandshakeResponse(response, future.GetCallback());
+  EXPECT_FALSE(future.Get());
+}
+
+// Tests that the handshake fails if the server's response includes a payload,
+// which is not allowed in the NN handshake pattern.
+TEST_F(SecureSessionAsyncImplTest, ProcessHandshakeResponseNonEmptyPlaintext) {
+  auto client_handshake_request = [&]() {
+    base::test::TestFuture<oak::session::v1::HandshakeRequest> future;
+    client_session_.GetHandshakeMessage(future.GetCallback());
+    return future.Get();
+  }();
+
+  TestServerSecureSession server_session;
+  // Generate a server response with a non-empty payload, which is invalid for
+  // the NN handshake pattern.
+  auto server_handshake_response = server_session.ProcessHandshake(
+      ConvertToHandshakeMessage(client_handshake_request), {1, 2, 3});
+  ASSERT_TRUE(server_handshake_response.has_value());
+
+  // The client should reject the response because the decrypted payload is not
+  // empty.
+  {
+    base::test::TestFuture<bool> future;
+    client_session_.ProcessHandshakeResponse(
+        ConvertToResponseProto(server_handshake_response.value()),
+        future.GetCallback());
+    EXPECT_FALSE(future.Get());
+  }
+}
+
+}  // namespace
+
+}  // namespace legion
diff --git a/components/metrics/private_metrics/BUILD.gn b/components/metrics/private_metrics/BUILD.gn
index 2e377af1..81b9cc1 100644
--- a/components/metrics/private_metrics/BUILD.gn
+++ b/components/metrics/private_metrics/BUILD.gn
@@ -70,6 +70,8 @@
     "private_metrics_reporting_service.h",
     "private_metrics_unsent_log_store_metrics.cc",
     "private_metrics_unsent_log_store_metrics.h",
+    "puma_histogram_encoder.cc",
+    "puma_histogram_encoder.h",
   ]
   public_deps = [
     ":dwa_recorder",
@@ -85,6 +87,7 @@
     "//base",
     "//build:buildflag_header_h",
     "//components/metrics",
+    "//components/metrics:library_support",
     "//components/metrics:metrics_pref_names",
     "//components/prefs",
     "//components/version_info",
@@ -103,6 +106,7 @@
     "//components/metrics/dwa/dwa_entry_builder_unittest.cc",
     "//components/metrics/dwa/dwa_recorder_unittest.cc",
     "//components/metrics/dwa/dwa_service_unittest.cc",
+    "//components/metrics/private_metrics/puma_histogram_encoder_unittest.cc",
     "data_upload_config_downloader_unittest.cc",
     "dkm_entry_builder_unittest.cc",
   ]
diff --git a/components/metrics/private_metrics/private_metrics_reporting_service.cc b/components/metrics/private_metrics/private_metrics_reporting_service.cc
index 3fe771b..b7ec5a4 100644
--- a/components/metrics/private_metrics/private_metrics_reporting_service.cc
+++ b/components/metrics/private_metrics/private_metrics_reporting_service.cc
@@ -23,19 +23,39 @@
 
 namespace metrics::private_metrics {
 
+namespace {
+
+bool IsDwaCompatiblityEnabled(std::optional<bool> dwa_compatibility) {
+  if (dwa_compatibility.has_value()) {
+    return dwa_compatibility.value();
+  }
+
+  return !base::FeatureList::IsEnabled(kPrivateMetricsFeature);
+}
+
+const char* GetLogDataPrefName(bool dwa_compatibility) {
+  if (dwa_compatibility) {
+    return dwa::prefs::kUnsentLogStoreName;
+  } else {
+    return prefs::kUnsentLogStoreName;
+  }
+}
+
+}  // namespace
+
 PrivateMetricsReportingService::PrivateMetricsReportingService(
     metrics::MetricsServiceClient* client,
     PrefService* local_state,
-    const UnsentLogStore::UnsentLogStoreLimits& storage_limits)
+    const UnsentLogStore::UnsentLogStoreLimits& storage_limits,
+    std::optional<bool> dwa_compatibility)
     : ReportingService(client,
                        local_state,
                        storage_limits.max_log_size_bytes,
                        /*logs_event_manager=*/nullptr),
+      dwa_compatibility_(IsDwaCompatiblityEnabled(dwa_compatibility)),
       unsent_log_store_(std::make_unique<PrivateMetricsUnsentLogStoreMetrics>(),
                         local_state,
-                        base::FeatureList::IsEnabled(kPrivateMetricsFeature)
-                            ? prefs::kUnsentLogStoreName
-                            : dwa::prefs::kUnsentLogStoreName,
+                        GetLogDataPrefName(dwa_compatibility_),
                         /*metadata_pref_name=*/nullptr,
                         storage_limits,
                         client->GetUploadSigningKey(),
@@ -59,10 +79,11 @@
 }
 
 GURL PrivateMetricsReportingService::GetUploadUrl() const {
-  if (base::FeatureList::IsEnabled(kPrivateMetricsFeature)) {
+  if (dwa_compatibility_) {
+    return metrics::GetDwaServerUrl();
+  } else {
     return metrics::GetPrivateMetricsServerUrl();
   }
-  return metrics::GetDwaServerUrl();
 }
 
 GURL PrivateMetricsReportingService::GetInsecureUploadUrl() const {
@@ -77,21 +98,22 @@
 
 metrics::MetricsLogUploader::MetricServiceType
 PrivateMetricsReportingService::service_type() const {
-  if (base::FeatureList::IsEnabled(kPrivateMetricsFeature)) {
+  if (dwa_compatibility_) {
+    return MetricsLogUploader::DWA;
+  } else {
     return MetricsLogUploader::PRIVATE_METRICS;
   }
-  return MetricsLogUploader::DWA;
 }
 
 void PrivateMetricsReportingService::LogCellularConstraint(
     bool upload_canceled) {
-  if (base::FeatureList::IsEnabled(private_metrics::kPrivateMetricsFeature)) {
+  if (dwa_compatibility_) {
+    base::UmaHistogramBoolean("DWA.LogUpload.Canceled.CellularConstraint",
+                              upload_canceled);
+  } else {
     base::UmaHistogramBoolean(
         "PrivateMetrics.LogUpload.Canceled.CellularConstraint",
         upload_canceled);
-  } else {
-    base::UmaHistogramBoolean("DWA.LogUpload.Canceled.CellularConstraint",
-                              upload_canceled);
   }
 }
 
@@ -100,21 +122,21 @@
                                                             bool was_https) {
   // `was_https` is ignored since all Private Metrics logs are received over
   // HTTPS.
-  if (base::FeatureList::IsEnabled(private_metrics::kPrivateMetricsFeature)) {
-    base::UmaHistogramSparse("PrivateMetrics.LogUpload.ResponseOrErrorCode",
+  if (dwa_compatibility_) {
+    base::UmaHistogramSparse("DWA.LogUpload.ResponseOrErrorCode",
                              response_code >= 0 ? response_code : error_code);
   } else {
-    base::UmaHistogramSparse("DWA.LogUpload.ResponseOrErrorCode",
+    base::UmaHistogramSparse("PrivateMetrics.LogUpload.ResponseOrErrorCode",
                              response_code >= 0 ? response_code : error_code);
   }
 }
 
 void PrivateMetricsReportingService::LogSuccessLogSize(size_t log_size) {
-  if (base::FeatureList::IsEnabled(private_metrics::kPrivateMetricsFeature)) {
+  if (dwa_compatibility_) {
+    base::UmaHistogramCounts10000("DWA.LogSize.OnSuccess", log_size / 1024);
+  } else {
     base::UmaHistogramCounts10000("PrivateMetrics.LogSize.OnSuccess",
                                   log_size / 1024);
-  } else {
-    base::UmaHistogramCounts10000("DWA.LogSize.OnSuccess", log_size / 1024);
   }
 }
 
diff --git a/components/metrics/private_metrics/private_metrics_reporting_service.h b/components/metrics/private_metrics/private_metrics_reporting_service.h
index 0e370f29..b1e4fe37 100644
--- a/components/metrics/private_metrics/private_metrics_reporting_service.h
+++ b/components/metrics/private_metrics/private_metrics_reporting_service.h
@@ -30,10 +30,15 @@
   // `local_state`, and `storage_limits`. Does not take ownership of the
   // parameters; instead stores a weak pointer to each. Caller should ensure
   // that the parameters are valid for the lifetime of this class.
+  //
+  // `dwa_compatibility` specifies whether the service should work in
+  // compatibility mode with the old DWA implementation. If `nullopt`, the value
+  // will be based on currently enabled flags.
   PrivateMetricsReportingService(
       metrics::MetricsServiceClient* client,
       PrefService* local_state,
-      const UnsentLogStore::UnsentLogStoreLimits& storage_limits);
+      const UnsentLogStore::UnsentLogStoreLimits& storage_limits,
+      std::optional<bool> dwa_compatibility = std::nullopt);
 
   PrivateMetricsReportingService(const PrivateMetricsReportingService&) =
       delete;
@@ -63,6 +68,10 @@
   void LogSuccessMetadata(const std::string& staged_log) override;
   void LogLargeRejection(size_t log_size) override;
 
+  // If true, this service works in a compatibility mode with old DWA
+  // implementation.
+  const bool dwa_compatibility_;
+
   metrics::UnsentLogStore unsent_log_store_;
 };
 
diff --git a/components/metrics/private_metrics/puma_histogram_encoder.cc b/components/metrics/private_metrics/puma_histogram_encoder.cc
new file mode 100644
index 0000000..24ff4b8c
--- /dev/null
+++ b/components/metrics/private_metrics/puma_histogram_encoder.cc
@@ -0,0 +1,41 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/private_metrics/puma_histogram_encoder.h"
+
+#include "base/metrics/histogram_snapshot_manager.h"
+#include "base/metrics/puma_histogram_functions.h"
+#include "base/metrics/statistics_recorder.h"
+#include "components/metrics/histogram_encoder.h"
+
+namespace metrics::private_metrics {
+
+using ::private_metrics::PrivateUserMetrics;
+
+PumaHistogramEncoder::PumaHistogramEncoder(PrivateUserMetrics& puma_proto)
+    : puma_proto_(&puma_proto) {}
+
+PumaHistogramEncoder::~PumaHistogramEncoder() = default;
+
+void PumaHistogramEncoder::RecordDelta(const base::HistogramBase& histogram,
+                                       const base::HistogramSamples& snapshot) {
+  EncodeHistogramDelta(histogram.histogram_name(), snapshot,
+                       puma_proto_->add_histogram_events());
+}
+
+// static
+void PumaHistogramEncoder::EncodeHistogramDeltas(
+    base::PumaType puma_type,
+    PrivateUserMetrics& puma_proto) {
+  PumaHistogramEncoder encoder(puma_proto);
+  base::HistogramSnapshotManager snapshot_manager(&encoder);
+
+  base::StatisticsRecorder::PrepareDeltas(
+      /*include_persistent=*/true,
+      /*flags_to_set=*/base::Histogram::kNoFlags,
+      /*required_flags=*/PumaTypeToHistogramFlags(puma_type),
+      &snapshot_manager);
+}
+
+}  // namespace metrics::private_metrics
diff --git a/components/metrics/private_metrics/puma_histogram_encoder.h b/components/metrics/private_metrics/puma_histogram_encoder.h
new file mode 100644
index 0000000..9d37ab5
--- /dev/null
+++ b/components/metrics/private_metrics/puma_histogram_encoder.h
@@ -0,0 +1,45 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PRIVATE_METRICS_PUMA_HISTOGRAM_ENCODER_H_
+#define COMPONENTS_METRICS_PRIVATE_METRICS_PUMA_HISTOGRAM_ENCODER_H_
+
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/puma_histogram_functions.h"
+#include "third_party/metrics_proto/private_metrics/private_user_metrics.pb.h"
+
+namespace metrics::private_metrics {
+
+// PumaHistogramEncoder is responsible for encoding histograms into PUMA protos,
+// which then can be used to upload PUMA records.
+class PumaHistogramEncoder : public base::HistogramFlattener {
+ public:
+  // Creates a new encoder which will encode histograms into the given proto.
+  explicit PumaHistogramEncoder(
+      ::private_metrics::PrivateUserMetrics& puma_proto);
+
+  PumaHistogramEncoder(const PumaHistogramEncoder&) = delete;
+  PumaHistogramEncoder& operator=(const PumaHistogramEncoder&) = delete;
+
+  ~PumaHistogramEncoder() override;
+
+  // Encodes histogram deltas (i.e. data logged since the last call) into the
+  // given PUMA proto. Only histograms with `required_flags` are included.
+  static void EncodeHistogramDeltas(
+      base::PumaType puma_type,
+      ::private_metrics::PrivateUserMetrics& puma_proto);
+
+ private:
+  // base::HistogramFlattener:
+  void RecordDelta(const base::HistogramBase& histogram,
+                   const base::HistogramSamples& snapshot) override;
+
+  // Target proto object where the histograms are being encoded.
+  raw_ptr<::private_metrics::PrivateUserMetrics> puma_proto_;
+};
+
+}  // namespace metrics::private_metrics
+
+#endif  // COMPONENTS_METRICS_PRIVATE_METRICS_PUMA_HISTOGRAM_ENCODER_H_
diff --git a/components/metrics/private_metrics/puma_histogram_encoder_unittest.cc b/components/metrics/private_metrics/puma_histogram_encoder_unittest.cc
new file mode 100644
index 0000000..68f8f1b
--- /dev/null
+++ b/components/metrics/private_metrics/puma_histogram_encoder_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/private_metrics/puma_histogram_encoder.h"
+
+#include "base/metrics/puma_histogram_functions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics::private_metrics {
+
+namespace {
+
+using ::metrics::HistogramEventProto;
+using ::private_metrics::PrivateUserMetrics;
+
+}  // namespace
+
+TEST(PumaHistogramEncoderTest, EncodesEmptyHistogramDeltas) {
+  PrivateUserMetrics puma_proto;
+
+  PumaHistogramEncoder::EncodeHistogramDeltas(base::PumaType::kRc, puma_proto);
+
+  EXPECT_EQ(puma_proto.histogram_events_size(), 0);
+}
+
+TEST(PumaHistogramEncoderTest, EncodesHistogramDeltas) {
+  base::PumaHistogramExactLinear(
+      base::PumaType::kRc, "PumaHistogramEncoderTest.TestHistogram", 12, 100);
+
+  PrivateUserMetrics puma_proto;
+
+  PumaHistogramEncoder::EncodeHistogramDeltas(base::PumaType::kRc, puma_proto);
+
+  EXPECT_EQ(puma_proto.histogram_events_size(), 1);
+
+  HistogramEventProto histogram_proto = puma_proto.histogram_events()[0];
+
+  EXPECT_TRUE(histogram_proto.has_name_hash());
+  EXPECT_EQ(histogram_proto.bucket_size(), 1);
+  EXPECT_EQ(histogram_proto.bucket()[0].count(), 1);
+}
+
+TEST(PumaHistogramEncoderTest, DoesNotDoubleEncodeHistogramDeltas) {
+  base::PumaHistogramBoolean(base::PumaType::kRc,
+                             "PumaHistogramEncoderTest.TestHistogram1", true);
+
+  PrivateUserMetrics puma_proto1;
+  PumaHistogramEncoder::EncodeHistogramDeltas(base::PumaType::kRc, puma_proto1);
+  EXPECT_EQ(puma_proto1.histogram_events_size(), 1);
+
+  PrivateUserMetrics puma_proto2;
+  PumaHistogramEncoder::EncodeHistogramDeltas(base::PumaType::kRc, puma_proto2);
+  EXPECT_EQ(puma_proto2.histogram_events_size(), 0);
+
+  base::PumaHistogramBoolean(base::PumaType::kRc,
+                             "PumaHistogramEncoderTest.TestHistogram2", true);
+
+  PrivateUserMetrics puma_proto3;
+  PumaHistogramEncoder::EncodeHistogramDeltas(base::PumaType::kRc, puma_proto3);
+  EXPECT_EQ(puma_proto3.histogram_events_size(), 1);
+}
+
+}  // namespace metrics::private_metrics
diff --git a/components/mirroring/service/BUILD.gn b/components/mirroring/service/BUILD.gn
index 20a2c0bd..0ffa716 100644
--- a/components/mirroring/service/BUILD.gn
+++ b/components/mirroring/service/BUILD.gn
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 import("//testing/test.gni")
-import("//third_party/libaom/options.gni")
 
 component("mirroring_service") {
   sources = [
diff --git a/components/network_session_configurator/browser/network_session_configurator.cc b/components/network_session_configurator/browser/network_session_configurator.cc
index 1306dc48..cf00378 100644
--- a/components/network_session_configurator/browser/network_session_configurator.cc
+++ b/components/network_session_configurator/browser/network_session_configurator.cc
@@ -22,7 +22,6 @@
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
-#include "components/network_session_configurator/common/network_features.h"
 #include "components/network_session_configurator/common/network_switches.h"
 #include "components/variations/variations_switches.h"
 #include "net/base/features.h"
diff --git a/components/network_session_configurator/browser/network_session_configurator_unittest.cc b/components/network_session_configurator/browser/network_session_configurator_unittest.cc
index 598c08c..7ded61a 100644
--- a/components/network_session_configurator/browser/network_session_configurator_unittest.cc
+++ b/components/network_session_configurator/browser/network_session_configurator_unittest.cc
@@ -15,7 +15,6 @@
 #include "base/strings/to_string.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
-#include "components/network_session_configurator/common/network_features.h"
 #include "components/network_session_configurator/common/network_switches.h"
 #include "components/variations/variations_associated_data.h"
 #include "net/base/features.h"
diff --git a/components/network_session_configurator/common/BUILD.gn b/components/network_session_configurator/common/BUILD.gn
index 7224734f..a4e3f1b 100644
--- a/components/network_session_configurator/common/BUILD.gn
+++ b/components/network_session_configurator/common/BUILD.gn
@@ -5,8 +5,6 @@
 component("common") {
   output_name = "network_session_configurator"
   sources = [
-    "network_features.cc",
-    "network_features.h",
     "network_session_configurator_export.h",
     "network_switch_list.h",
     "network_switches.cc",
diff --git a/components/network_session_configurator/common/network_features.cc b/components/network_session_configurator/common/network_features.cc
deleted file mode 100644
index c1bd42fa..0000000
--- a/components/network_session_configurator/common/network_features.cc
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/network_session_configurator/common/network_features.h"
-
-#include "build/build_config.h"
-
-namespace features {
-
-}  // namespace features
diff --git a/components/network_session_configurator/common/network_features.h b/components/network_session_configurator/common/network_features.h
deleted file mode 100644
index 84d71f1..0000000
--- a/components/network_session_configurator/common/network_features.h
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_NETWORK_SESSION_CONFIGURATOR_COMMON_NETWORK_FEATURES_H_
-#define COMPONENTS_NETWORK_SESSION_CONFIGURATOR_COMMON_NETWORK_FEATURES_H_
-
-#include "base/feature_list.h"
-#include "network_session_configurator_export.h"
-
-namespace features {
-
-}  // namespace features
-
-#endif  // COMPONENTS_NETWORK_SESSION_CONFIGURATOR_COMMON_NETWORK_FEATURES_H_
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index 724de88..d0ff238 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit 724de88d56f431a79762af82b5efec95e6601191
+Subproject commit d0ff238e3fc92066db0aebb246f7203918a57de5
diff --git a/components/optimization_guide/proto/features/ios_smart_tab_grouping.proto b/components/optimization_guide/proto/features/ios_smart_tab_grouping.proto
index 6d56d7d..bc86ed5 100644
--- a/components/optimization_guide/proto/features/ios_smart_tab_grouping.proto
+++ b/components/optimization_guide/proto/features/ios_smart_tab_grouping.proto
@@ -42,7 +42,7 @@
   repeated Tab tabs = 1;
 
   // All pre-existing tab groups.
-  repeated TabGroup pre_existing_tab_groups = 2;
+  repeated TabGroupMinimal pre_existing_tab_groups = 2;
 
   // Whether or not the tab organization response can include reorganizing
   // existing tab groups.
@@ -52,13 +52,6 @@
   string user_command = 4 [features = { field_presence: EXPLICIT }];
 }
 
-// The response for the iOS Smart Tab Grouping feature.
-// Next ID: 2
-message IosSmartTabGroupingResponse {
-  // Resulting tab groups.
-  repeated TabGroupMinimal tab_groups = 1;
-}
-
 // The minimal representation of a tab group for iOS Smart Tab Grouping.
 // Next ID: 5
 message TabGroupMinimal {
@@ -74,3 +67,10 @@
   // The emoji that represents this tab group, encoded in UTF-8.
   string emoji = 4 [features = { field_presence: EXPLICIT }];
 }
+
+// The response for the iOS Smart Tab Grouping feature.
+// Next ID: 2
+message IosSmartTabGroupingResponse {
+  // Resulting tab groups.
+  repeated TabGroupMinimal tab_groups = 1;
+}
diff --git a/components/password_manager/ios/resources/password_controller.ts b/components/password_manager/ios/resources/password_controller.ts
index 3c3636d843..8d8a1937 100644
--- a/components/password_manager/ios/resources/password_controller.ts
+++ b/components/password_manager/ios/resources/password_controller.ts
@@ -4,6 +4,7 @@
 
 import * as fillConstants from '//components/autofill/ios/form_util/resources/fill_constants.js';
 import * as fillUtil from '//components/autofill/ios/form_util/resources/fill_util.js';
+import {webFormElementToFormData} from '//components/autofill/ios/form_util/resources/fill_web_form.js';
 import {getFormControlElements} from '//components/autofill/ios/form_util/resources/form_utils.js';
 import {gCrWebLegacy} from '//ios/web/public/js_messaging/resources/gcrweb.js';
 import {isTextField, sendWebKitMessage} from '//ios/web/public/js_messaging/resources/utils.js';
@@ -413,9 +414,8 @@
 function getPasswordFormData(
     formElement: HTMLFormElement): fillUtil.AutofillFormData|null {
   const formData = {} as fillUtil.AutofillFormData;
-  const ok = gCrWebLegacy.fill.webFormElementToFormData(
-      window, formElement, /*formControlElement=*/ null, formData,
-      /*field=*/ null);
+  const ok = webFormElementToFormData(
+      window, formElement, /*formControlElement=*/ null, formData);
   return ok ? formData : null;
 }
 
diff --git a/components/policy/resources/templates/policy_definitions/Proxy/ProxyOverrideRules.yaml b/components/policy/resources/templates/policy_definitions/Proxy/ProxyOverrideRules.yaml
index 130c8edb1..434c3fe 100644
--- a/components/policy/resources/templates/policy_definitions/Proxy/ProxyOverrideRules.yaml
+++ b/components/policy/resources/templates/policy_definitions/Proxy/ProxyOverrideRules.yaml
@@ -4,9 +4,13 @@
 
   Leaving the policy unset has no effect on other proxy policies or user settings.
 
-  When the browser needs to decide what proxy to use, entries of the <ph name="PROXY_OVERRIDE_RULES_POLICY_NAME">ProxyOverrideRules</ph> policy are evaluated in sequence until an entry has a match for at least one of the URL hosts in <ph name="PROXY_OVERRIDE_RULES_DESTINATION_MATCHERS">DestinationMatchers</ph> and if all entries in <ph name="PROXY_OVERRIDE_RULES_CONDITIONS">Conditions</ph> are met. The value in <ph name="PROXY_OVERRIDE_RULES_PROXY_LIST">ProxyList</ph> will be used as a proxy for such a match. If no match is found, proxy settings will fall back to what is set in the <ph name="PROXY_SETTINGS_POLICY_NAME">ProxySettings</ph> policy.
+  When the browser needs to decide what proxy to use, entries of the <ph name="PROXY_OVERRIDE_RULES_POLICY_NAME">ProxyOverrideRules</ph> policy are evaluated in sequence until an entry satisfies all the following points:
+    * At least one of the URL patterns in <ph name="PROXY_OVERRIDE_RULES_DESTINATION_MATCHERS">DestinationMatchers</ph> is matched.
+    * None of the URL patterns in <ph name="PROXY_OVERRIDE_RULES_EXCLUDE_DESTINATION_MATCHERS">ExcludeDestinationMatchers</ph> is matched.
+    * If <ph name="PROXY_OVERRIDE_RULES_CONDITIONS">Conditions</ph> is specified and is a non-empty list, all conditions represented by its entries are satisfied.
+  The value in <ph name="PROXY_OVERRIDE_RULES_PROXY_LIST">ProxyList</ph> will be used as a proxy for such a match. If no match is found, proxy settings will fall back to what is set in the <ph name="PROXY_SETTINGS_POLICY_NAME">ProxySettings</ph> policy.
 
-  The URL patterns supported by <ph name="PROXY_OVERRIDE_RULES_DESTINATION_MATCHERS">DestinationMatchers</ph> are documented at https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md#proxy-config-url-patterns.
+  The URL patterns supported by <ph name="PROXY_OVERRIDE_RULES_DESTINATION_MATCHERS">DestinationMatchers</ph> and <ph name="PROXY_OVERRIDE_RULES_EXCLUDE_DESTINATION_MATCHERS">ExcludeDestinationMatchers</ph> are documented at https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md#proxy-config-url-patterns.
 
   The <ph name="PROXY_OVERRIDE_RULES_PROXY_LIST">ProxyList</ph> field entries correspond to string values of PAC files such as:
     * DIRECT
@@ -23,7 +27,7 @@
 
   The <ph name="PROXY_OVERRIDE_RULES_CONDITIONS">Conditions</ph> field contains a list of conditions that must all be met for its override rule to be used to decide the proxy to use. If it is left unset, the entry will be used as long as at least one host in <ph name="PROXY_OVERRIDE_RULES_DESTINATION_MATCHERS">DestinationMatchers</ph> is matched.
 
-  The <ph name="PROXY_OVERRIDE_RULES_DNS_PROBE">DnsProbe</ph> condition checks if the provided DNS <ph name="PROXY_OVERRIDE_RULES_HOST">Host</ph> is able to resolve to an IP address. If the value of <ph name="PROXY_OVERRIDE_RULES_RESULT">Result</ph> is "<ph name="PROXY_OVERRIDE_RULES_RESOLVES">resolves</ph>" then the condition is considered as met. If the value is instead set to <ph name="PROXY_OVERRIDE_RULES_NOT_FOUND">not_found</ph>, then the condition is considered met if the resolution failed.
+  The <ph name="PROXY_OVERRIDE_RULES_DNS_PROBE">DnsProbe</ph> condition checks if the provided DNS <ph name="PROXY_OVERRIDE_RULES_HOST">Host</ph> is able to resolve to an IP address. If the value of <ph name="PROXY_OVERRIDE_RULES_RESULT">Result</ph> is "<ph name="PROXY_OVERRIDE_RULES_RESOLVED">resolved</ph>" then the condition is considered as met. If the value is instead set to <ph name="PROXY_OVERRIDE_RULES_NOT_FOUND">not_found</ph>, then the condition is considered met if the resolution failed.
 
 example_value:
 - DestinationMatchers:
@@ -35,7 +39,14 @@
   Conditions:
   - DnsProbe:
       Host: 'corp.ads'
-      Result: "resolves"
+      Result: "resolved"
+- DestinationMatchers:
+  - 'https://google.com'
+  ExcludeDestinationMatchers:
+  - 'https://mail.google.com'
+  ProxyList:
+  - 'HTTPS proxy.app:443'
+  - 'DIRECT'
 features:
   cloud_only: true
   dynamic_refresh: true
@@ -57,6 +68,10 @@
         type: array
         items:
           type: string
+      ExcludeDestinationMatchers:
+        type: array
+        items:
+          type: string
       ProxyList:
         type: array
         items:
@@ -74,7 +89,7 @@
                 Result:
                   type: string
                   enum:
-                  - resolves
+                  - resolved
                   - not_found
 type: dict
 tags:
diff --git a/components/policy/test/data/pref_mapping/ProxyOverrideRules.json b/components/policy/test/data/pref_mapping/ProxyOverrideRules.json
index a043c46..14ea2d9b 100644
--- a/components/policy/test/data/pref_mapping/ProxyOverrideRules.json
+++ b/components/policy/test/data/pref_mapping/ProxyOverrideRules.json
@@ -25,7 +25,7 @@
                       {
                           "DnsProbe": {
                               "Host": "corp.ads",
-                              "Result": "resolves"
+                              "Result": "resolved"
                           }
                       }
                   ]
diff --git a/components/proxy_config/pref_proxy_config_tracker_impl.cc b/components/proxy_config/pref_proxy_config_tracker_impl.cc
index 4df1abf0..0f62fb1 100644
--- a/components/proxy_config/pref_proxy_config_tracker_impl.cc
+++ b/components/proxy_config/pref_proxy_config_tracker_impl.cc
@@ -63,7 +63,7 @@
   // {
   //   "DnsProbe": {
   //     "Host": "corp.ads",
-  //     "Result": "resolves", // or "not_found"
+  //     "Result": "resolved", // or "not_found"
   //   }
   // }
   // For now "DnsProbe" is always expected, but eventually other types of
@@ -77,44 +77,70 @@
   const std::string* host_value = dns_probe_value->FindString("Host");
   const std::string* result_value = dns_probe_value->FindString("Result");
   if (!host_value || !result_value ||
-      (*result_value != "resolves" && *result_value != "not_found")) {
+      (*result_value != "resolved" && *result_value != "not_found")) {
     return std::nullopt;
   }
 
   net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition dns_probe_condition;
   dns_probe_condition.host = url::SchemeHostPort(GURL(*host_value));
   dns_probe_condition.result =
-      *result_value == "resolves"
-          ? net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kResolves
+      *result_value == "resolved"
+          ? net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kResolved
           : net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kNotFound;
   return dns_probe_condition;
 }
 
-// Returns false if an unexpected value was found in the passed `value`.
-bool AddDestinationMatchers(const base::Value::Dict& value,
-                            net::ProxyConfig::ProxyOverrideRule& rule) {
+// Generic parser for a list of URL patterns to populate into the passed
+// `rules`. Returns false if an unexpected value was found in the passed
+// `value`. `optional_field` indicates if the matcher list is required to be
+// present in the rule or not, and will be returned directly if `value` doesn't
+// have an entry for `key`.
+//
+// Implicit rules are not applied to URL pattern listsof the
+// "ProxyOverrideRules" policy, so on a valid `value` there is always an extra
+// rule added to subtract them from the matcher evaluation.
+bool AddUrlMatcher(const base::Value::Dict& value,
+                   net::ProxyHostMatchingRules& rules,
+                   const std::string& key,
+                   bool optional_field) {
   // Expected schema:
   // {
   //   ...
-  //   "DestinationMatchers": [ "https://app1.com", "https://app2.com" ],
+  //   "<key>": [ "https://app1.com", "https://app2.com" ],
   //   ...
   // }
-  auto* destination_matchers_value = value.FindList("DestinationMatchers");
-  if (!destination_matchers_value) {
-    return false;
+  auto* matchers_value = value.FindList(key);
+  if (!matchers_value) {
+    rules.AddRulesToSubtractImplicit();
+    return optional_field;
   }
 
-  for (const auto& matcher : *destination_matchers_value) {
+  for (const auto& matcher : *matchers_value) {
     if (matcher.is_string()) {
-      rule.destination_matchers.AddRuleFromString(matcher.GetString());
+      rules.AddRuleFromString(matcher.GetString());
     } else {
       return false;
     }
   }
 
+  rules.AddRulesToSubtractImplicit();
   return true;
 }
 
+// Returns false if an unexpected value was found in the passed `value`.
+bool AddDestinationMatchers(const base::Value::Dict& value,
+                            net::ProxyConfig::ProxyOverrideRule& rule) {
+  return AddUrlMatcher(value, rule.destination_matchers, "DestinationMatchers",
+                       /*optional_field=*/false);
+}
+
+// Returns false if an unexpected value was found in the passed `value`.
+bool AddExcludeDestinationMatchers(const base::Value::Dict& value,
+                                   net::ProxyConfig::ProxyOverrideRule& rule) {
+  return AddUrlMatcher(value, rule.exclude_destination_matchers,
+                       "ExcludeDestinationMatchers", /*optional_field=*/true);
+}
+
 // Returns false if an unexpected value was found in the passed `value`, or if
 // the "ProxyList" key is missing.
 bool AddProxyChain(const base::Value::Dict& value,
@@ -175,7 +201,7 @@
   //        {
   //            "DnsProbe": {
   //                "Host": "corp.ads",
-  //                "Result": "resolves"
+  //                "Result": "resolved"
   //            }
   //        }
   //   ]
@@ -208,12 +234,13 @@
   // Expected schema:
   // {
   //   "DestinationMatchers": [ "https://app1.com", "https://app2.com" ],
+  //   "ExcludeDestinationMatchers: ["https://exception.com"],
   //   "ProxyList": [ "HTTPS proxy.app:443", "DIRECT" ],
   //   "Conditions": [
   //        {
   //            "DnsProbe": {
   //                "Host": "corp.ads",
-  //                "Result": "resolves"
+  //                "Result": "resolved"
   //            }
   //        }
   //   ]
@@ -221,8 +248,9 @@
   const base::Value::Dict& dict = value.GetDict();
   net::ProxyConfig::ProxyOverrideRule rule;
 
-  if (!AddDestinationMatchers(dict, rule) || !AddProxyChain(dict, rule) ||
-      !AddConditions(dict, rule)) {
+  if (!AddDestinationMatchers(dict, rule) ||
+      !AddExcludeDestinationMatchers(dict, rule) ||
+      !AddProxyChain(dict, rule) || !AddConditions(dict, rule)) {
     return std::nullopt;
   }
 
diff --git a/components/proxy_config/pref_proxy_config_tracker_impl_unittest.cc b/components/proxy_config/pref_proxy_config_tracker_impl_unittest.cc
index 5abe02d..a6eca0c 100644
--- a/components/proxy_config/pref_proxy_config_tracker_impl_unittest.cc
+++ b/components/proxy_config/pref_proxy_config_tracker_impl_unittest.cc
@@ -514,6 +514,9 @@
                      "https://some.app.com",
                      "https://other.app.com",
                  ],
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.app.com",
+                 ],
                  "ProxyList": [
                      "HTTPS proxy.app:443",
                      "DIRECT",
@@ -522,7 +525,7 @@
                      {
                          "DnsProbe": {
                              "Host": "corp.ads",
-                             "Result": "resolves",
+                             "Result": "resolved",
                          },
                      }
                  ]
@@ -536,11 +539,18 @@
   EXPECT_EQ(actual_config.value().proxy_override_rules().size(), 1u);
 
   const auto& rule = actual_config.value().proxy_override_rules().at(0);
-  EXPECT_EQ(rule.destination_matchers.rules().size(), 2u);
+  EXPECT_EQ(rule.destination_matchers.rules().size(), 3u);
   EXPECT_EQ(rule.destination_matchers.rules().at(0)->ToString(),
             "https://some.app.com");
   EXPECT_EQ(rule.destination_matchers.rules().at(1)->ToString(),
             "https://other.app.com");
+  EXPECT_EQ(rule.destination_matchers.rules().at(2)->ToString(), "<-loopback>");
+
+  EXPECT_EQ(rule.exclude_destination_matchers.rules().size(), 2u);
+  EXPECT_EQ(rule.exclude_destination_matchers.rules().at(0)->ToString(),
+            "https://exception.some.app.com");
+  EXPECT_EQ(rule.exclude_destination_matchers.rules().at(1)->ToString(),
+            "<-loopback>");
 
   EXPECT_EQ(rule.proxy_list.size(), 2u);
   EXPECT_EQ(rule.proxy_list.AllChains().at(0),
@@ -552,7 +562,7 @@
   EXPECT_EQ(rule.dns_conditions.at(0).host,
             url::SchemeHostPort(GURL("corp.ads")));
   EXPECT_EQ(rule.dns_conditions.at(0).result,
-            net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kResolves);
+            net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kResolved);
 
   // Setting the pref again in the same test scope validates the policy is
   // dynamic and that the retrieved config changes appropriately.
@@ -572,7 +582,7 @@
                      {
                          "DnsProbe": {
                              "Host": "corp.ads",
-                             "Result": "resolves",
+                             "Result": "resolved",
                          },
                      },
                      {
@@ -587,6 +597,9 @@
                  "DestinationMatchers": [
                      "https://some.special.app.com",
                  ],
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.special.app.com",
+                 ],
                  "ProxyList": [
                      "DIRECT",
                  ],
@@ -600,9 +613,15 @@
   EXPECT_EQ(updated_config.value().proxy_override_rules().size(), 2u);
 
   const auto& rule_0 = updated_config.value().proxy_override_rules().at(0);
-  EXPECT_EQ(rule_0.destination_matchers.rules().size(), 1u);
+  EXPECT_EQ(rule_0.destination_matchers.rules().size(), 2u);
   EXPECT_EQ(rule_0.destination_matchers.rules().at(0)->ToString(),
             "https://some.other.app.com");
+  EXPECT_EQ(rule_0.destination_matchers.rules().at(1)->ToString(),
+            "<-loopback>");
+
+  EXPECT_EQ(rule_0.exclude_destination_matchers.rules().size(), 1u);
+  EXPECT_EQ(rule_0.exclude_destination_matchers.rules().at(0)->ToString(),
+            "<-loopback>");
 
   EXPECT_EQ(rule_0.proxy_list.size(), 4u);
   EXPECT_EQ(rule_0.proxy_list.AllChains().at(0),
@@ -618,16 +637,24 @@
   EXPECT_EQ(rule_0.dns_conditions.at(0).host,
             url::SchemeHostPort(GURL("corp.ads")));
   EXPECT_EQ(rule_0.dns_conditions.at(0).result,
-            net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kResolves);
+            net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kResolved);
   EXPECT_EQ(rule_0.dns_conditions.at(1).host,
             url::SchemeHostPort(GURL("ads.corps")));
   EXPECT_EQ(rule_0.dns_conditions.at(1).result,
             net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kNotFound);
 
   const auto& rule_1 = updated_config.value().proxy_override_rules().at(1);
-  EXPECT_EQ(rule_1.destination_matchers.rules().size(), 1u);
+  EXPECT_EQ(rule_1.destination_matchers.rules().size(), 2u);
   EXPECT_EQ(rule_1.destination_matchers.rules().at(0)->ToString(),
             "https://some.special.app.com");
+  EXPECT_EQ(rule_1.destination_matchers.rules().at(1)->ToString(),
+            "<-loopback>");
+
+  EXPECT_EQ(rule_1.exclude_destination_matchers.rules().size(), 2u);
+  EXPECT_EQ(rule_1.exclude_destination_matchers.rules().at(0)->ToString(),
+            "https://exception.some.special.app.com");
+  EXPECT_EQ(rule_1.exclude_destination_matchers.rules().at(1)->ToString(),
+            "<-loopback>");
 
   EXPECT_EQ(rule_1.proxy_list.size(), 1u);
   EXPECT_EQ(rule_1.proxy_list.AllChains().at(0),
@@ -682,7 +709,7 @@
                      {
                          "DnsProbe": {
                              "Host": "corp.ads",
-                             "Result": "resolves",
+                             "Result": "resolved",
                          },
                      }
                  ]
@@ -696,11 +723,16 @@
   EXPECT_EQ(actual_config.value().proxy_override_rules().size(), 1u);
 
   const auto& rule = actual_config.value().proxy_override_rules().at(0);
-  EXPECT_EQ(rule.destination_matchers.rules().size(), 2u);
+  EXPECT_EQ(rule.destination_matchers.rules().size(), 3u);
   EXPECT_EQ(rule.destination_matchers.rules().at(0)->ToString(),
             "https://some.app.com");
   EXPECT_EQ(rule.destination_matchers.rules().at(1)->ToString(),
             "https://other.app.com");
+  EXPECT_EQ(rule.destination_matchers.rules().at(2)->ToString(), "<-loopback>");
+
+  EXPECT_EQ(rule.exclude_destination_matchers.rules().size(), 1u);
+  EXPECT_EQ(rule.exclude_destination_matchers.rules().at(0)->ToString(),
+            "<-loopback>");
 
   EXPECT_EQ(rule.proxy_list.size(), 3u);
   EXPECT_EQ(rule.proxy_list.AllChains().at(0),
@@ -714,7 +746,7 @@
   EXPECT_EQ(rule.dns_conditions.at(0).host,
             url::SchemeHostPort(GURL("corp.ads")));
   EXPECT_EQ(rule.dns_conditions.at(0).result,
-            net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kResolves);
+            net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kResolved);
 }
 
 TEST_F(PrefProxyConfigOverrideRulesTest, NonListValues) {
@@ -756,6 +788,9 @@
                      "https://some.app.com",
                      "https://other.app.com",
                  ],
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.app.com",
+                 ],
                  "ProxyList": [
                      "HTTPS proxy.app:443",
                      "DIRECT",
@@ -764,7 +799,7 @@
                      {
                          "DnsProbe": {
                              "Host": "corp.ads",
-                             "Result": "resolves",
+                             "Result": "resolved",
                          },
                      }
                  ]
@@ -774,11 +809,14 @@
                      "https://some.app.com",
                      "https://other.app.com",
                  ],
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.app.com",
+                 ],
                  "Conditions": [
                      {
                          "DnsProbe": {
                              "Host": "corp.ads",
-                             "Result": "resolves",
+                             "Result": "resolved",
                          },
                      }
                  ]
@@ -788,6 +826,9 @@
                      "https://some.app.com",
                      "https://other.app.com",
                  ],
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.app.com",
+                 ],
                  "ProxyList": [
                      "HTTPS proxy.app:443",
                      1234,
@@ -796,12 +837,36 @@
                      {
                          "DnsProbe": {
                              "Host": "corp.ads",
-                             "Result": "resolves",
+                             "Result": "resolved",
                          },
                      }
                  ]
              },
              {
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.app.com",
+                 ],
+                 "ProxyList": [
+                     "HTTPS proxy.app:443",
+                     "DIRECT",
+                 ],
+                 "Conditions": [
+                     {
+                         "DnsProbe": {
+                             "Host": "corp.ads",
+                             "Result": "resolved",
+                         },
+                     }
+                 ]
+             },
+             {
+                 "DestinationMatchers": [
+                     "https://some.app.com",
+                     1234,
+                 ],
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.app.com",
+                 ],
                  "ProxyList": [
                      "HTTPS proxy.app:443",
                      "DIRECT",
@@ -818,6 +883,9 @@
              {
                  "DestinationMatchers": [
                      "https://some.app.com",
+                     "https://other.app.com",
+                 ],
+                 "ExcludeDestinationMatchers": [
                      1234,
                  ],
                  "ProxyList": [
@@ -828,7 +896,7 @@
                      {
                          "DnsProbe": {
                              "Host": "corp.ads",
-                             "Result": "resolves",
+                             "Result": "resolved",
                          },
                      }
                  ]
@@ -838,6 +906,9 @@
                      "https://some.app.com",
                      "https://other.app.com",
                  ],
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.app.com",
+                 ],
                  "ProxyList": [
                      "HTTPS proxy.app:443",
                      "DIRECT",
@@ -851,6 +922,9 @@
                      "https://some.app.com",
                      "https://other.app.com",
                  ],
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.app.com",
+                 ],
                  "ProxyList": [
                      "HTTPS proxy.app:443",
                      "DIRECT",
@@ -858,7 +932,7 @@
                  "Conditions": [
                      {
                          "DnsProbe": {
-                             "Result": "resolves",
+                             "Result": "resolved",
                          },
                      }
                  ]
@@ -868,6 +942,9 @@
                      "https://some.app.com",
                      "https://other.app.com",
                  ],
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.app.com",
+                 ],
                  "ProxyList": [
                      "HTTPS proxy.app:443",
                      "DIRECT",
@@ -885,6 +962,9 @@
                      "https://some.app.com",
                      "https://other.app.com",
                  ],
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.app.com",
+                 ],
                  "ProxyList": [
                      "HTTPS proxy.app:443",
                      "DIRECT",
@@ -903,6 +983,9 @@
                      "https://some.app.com",
                      "https://other.app.com",
                  ],
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.app.com",
+                 ],
                  "ProxyList": [
                      "HTTPS proxy.app:443",
                      "DIRECT",
@@ -911,7 +994,7 @@
                      {
                          "DnsProbe": {
                              "Host": 1234,
-                             "Result": "resolves",
+                             "Result": "resolved",
                          },
                      }
                  ]
@@ -921,6 +1004,9 @@
                      "https://some.app.com",
                      "https://other.app.com",
                  ],
+                 "ExcludeDestinationMatchers": [
+                     "https://exception.some.app.com",
+                 ],
                  "ProxyList": [
                      "HTTPS proxy.app:443",
                      "DIRECT",
@@ -942,11 +1028,18 @@
   EXPECT_EQ(actual_config.value().proxy_override_rules().size(), 1u);
 
   const auto& rule = actual_config.value().proxy_override_rules().at(0);
-  EXPECT_EQ(rule.destination_matchers.rules().size(), 2u);
+  EXPECT_EQ(rule.destination_matchers.rules().size(), 3u);
   EXPECT_EQ(rule.destination_matchers.rules().at(0)->ToString(),
             "https://some.app.com");
   EXPECT_EQ(rule.destination_matchers.rules().at(1)->ToString(),
             "https://other.app.com");
+  EXPECT_EQ(rule.destination_matchers.rules().at(2)->ToString(), "<-loopback>");
+
+  EXPECT_EQ(rule.exclude_destination_matchers.rules().size(), 2u);
+  EXPECT_EQ(rule.exclude_destination_matchers.rules().at(0)->ToString(),
+            "https://exception.some.app.com");
+  EXPECT_EQ(rule.exclude_destination_matchers.rules().at(1)->ToString(),
+            "<-loopback>");
 
   EXPECT_EQ(rule.proxy_list.size(), 2u);
   EXPECT_EQ(rule.proxy_list.AllChains().at(0),
@@ -958,7 +1051,7 @@
   EXPECT_EQ(rule.dns_conditions.at(0).host,
             url::SchemeHostPort(GURL("corp.ads")));
   EXPECT_EQ(rule.dns_conditions.at(0).result,
-            net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kResolves);
+            net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kResolved);
 }
 
 }  // namespace
diff --git a/components/remote_cocoa/app_shim/application_bridge.h b/components/remote_cocoa/app_shim/application_bridge.h
index b141f3d..70f4eb9 100644
--- a/components/remote_cocoa/app_shim/application_bridge.h
+++ b/components/remote_cocoa/app_shim/application_bridge.h
@@ -18,6 +18,12 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 
+#if defined(__OBJC__)
+@class NativeWidgetMacNSWindow;
+#else
+class NativeWidgetMacNSWindow;
+#endif
+
 namespace system_media_controls {
 class SystemMediaControlsBridge;
 }  // namespace system_media_controls
@@ -57,6 +63,9 @@
       RenderWidgetHostNSViewCreateCallback render_widget_host_create_callback,
       WebContentsNSViewCreateCallback web_contents_create_callback);
 
+  void SetNSWindowCreatedCallbackForTesting(
+      base::RepeatingCallback<void(NativeWidgetMacNSWindow*)> callback);
+
   // mojom::Application:
   void CreateAlert(
       mojo::PendingReceiver<mojom::AlertBridge> bridge_receiver) override;
@@ -94,6 +103,8 @@
 
   RenderWidgetHostNSViewCreateCallback render_widget_host_create_callback_;
   WebContentsNSViewCreateCallback web_contents_create_callback_;
+  base::RepeatingCallback<void(NativeWidgetMacNSWindow*)>
+      ns_window_created_callback_;
 
   std::unique_ptr<system_media_controls::SystemMediaControlsBridge>
       system_media_controls_bridge_;
diff --git a/components/remote_cocoa/app_shim/application_bridge.mm b/components/remote_cocoa/app_shim/application_bridge.mm
index b5801f8..d5afd4b 100644
--- a/components/remote_cocoa/app_shim/application_bridge.mm
+++ b/components/remote_cocoa/app_shim/application_bridge.mm
@@ -6,6 +6,7 @@
 
 #include <tuple>
 
+#include "base/check_is_test.h"
 #include "base/functional/bind.h"
 #include "base/no_destructor.h"
 #include "components/remote_cocoa/app_shim/alert.h"
@@ -44,6 +45,8 @@
                        base::Unretained(this)));
   }
 
+  NativeWidgetNSWindowBridge* bridge() { return bridge_.get(); }
+
  private:
   ~NativeWidgetBridgeOwner() override = default;
 
@@ -135,6 +138,11 @@
   web_contents_create_callback_ = web_contents_create_callback;
 }
 
+void ApplicationBridge::SetNSWindowCreatedCallbackForTesting(  // IN-TEST
+    base::RepeatingCallback<void(NativeWidgetMacNSWindow*)> callback) {
+  ns_window_created_callback_ = std::move(callback);
+}
+
 void ApplicationBridge::CreateAlert(
     mojo::PendingReceiver<mojom::AlertBridge> bridge_receiver) {
   // The resulting object manages its own lifetime.
@@ -148,9 +156,14 @@
     mojo::PendingAssociatedRemote<mojom::NativeWidgetNSWindowHost> host,
     mojo::PendingAssociatedRemote<mojom::TextInputHost> text_input_host) {
   // The resulting object will be destroyed when its message pipe is closed.
-  std::ignore =
+  auto* bridge_owner =
       new NativeWidgetBridgeOwner(bridge_id, std::move(bridge_receiver),
                                   std::move(host), std::move(text_input_host));
+  if (ns_window_created_callback_) {
+    CHECK_IS_TEST();
+    bridge_owner->bridge()->OnWindowSetForTesting(  // IN-TEST
+        ns_window_created_callback_);
+  }
 }
 
 void ApplicationBridge::CreateRenderWidgetHostNSView(
diff --git a/components/remote_cocoa/app_shim/native_widget_mac_nswindow.h b/components/remote_cocoa/app_shim/native_widget_mac_nswindow.h
index 71158ca..020050d 100644
--- a/components/remote_cocoa/app_shim/native_widget_mac_nswindow.h
+++ b/components/remote_cocoa/app_shim/native_widget_mac_nswindow.h
@@ -77,8 +77,8 @@
 // window, so this function prevents that if the window is currently inactive.
 - (void)orderFrontKeepWindowKeyState;
 
-// Overridden to prevent headless windows to be constrained to the physical
-// screen bounds.
+// Overrides NSWindow's frame constraining to prevent AppKit's adjustments
+// so child windows aren't pushed down due to invisible collision.
 - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen;
 
 // Is the window a part of a browser window tree that is currently in an
diff --git a/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm b/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
index e00bf1b..a474e70c 100644
--- a/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
+++ b/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
@@ -352,18 +352,12 @@
 }
 
 - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen {
-  // Headless windows should not be constrained within the physical screen.
-  if (_isHeadless) {
-    return frameRect;
-  }
-
-  if (base::mac::MacOSVersion() >= 26'00'00) {
-    // Since macOS 26.0, when the main window enters full screen, the
-    // overlay child window’s position is automatically adjusted by the system
-    // to just below the menu bar location (even though the menu bar is hidden
-    // at that time). During this process, [NSWindow constrainFrameRect] is
-    // called and returns the system-adjusted position. Override this
-    // function to return the original value frameRect before the adjustment.
+  if (_isHeadless || self.parentWindow) {
+    // AppKit's default implementation moves child windows down to avoid
+    // the menu bar. We don't want that behavior, because widgets like the
+    // Omnibox may have a big shadow that could cause invisible menu bar
+    // collision in fullscreen/maximized state. We override it here to
+    // return the original frameRect before the adjustment.
     return frameRect;
   }
 
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h
index 7b5f8c8..6a7d3fc 100644
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h
@@ -111,6 +111,9 @@
   // explicitly set an NSWindow in this way.
   void SetWindow(NativeWidgetMacNSWindow* window);
 
+  void OnWindowSetForTesting(
+      base::OnceCallback<void(NativeWidgetMacNSWindow*)> callback);
+
   // Set the command dispatcher delegate for the window. This will retain
   // |delegate| for the lifetime of |this|.
   void SetCommandDispatcher(
@@ -496,6 +499,8 @@
   // immersive_mode_controller_ resets.
   int immersive_fullscreen_reveal_lock_count_ = 0;
 
+  base::OnceCallback<void(NativeWidgetMacNSWindow*)> window_set_callback_;
+
   base::WeakPtrFactory<NativeWidgetNSWindowBridge> factory_{this};
 };
 
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
index f3ea2bf..32c5dbcc 100644
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
@@ -513,6 +513,14 @@
 void NativeWidgetNSWindowBridge::CreateWindow(
     mojom::CreateWindowParamsPtr params) {
   SetWindow(CreateNSWindow(params.get()));
+  if (window_set_callback_) {
+    std::move(window_set_callback_).Run(ns_window());
+  }
+}
+
+void NativeWidgetNSWindowBridge::OnWindowSetForTesting(  // IN-TEST
+    base::OnceCallback<void(NativeWidgetMacNSWindow*)> callback) {
+  window_set_callback_ = std::move(callback);
 }
 
 void NativeWidgetNSWindowBridge::InitWindow(
diff --git a/components/resources/BUILD.gn b/components/resources/BUILD.gn
index cd6586b3..3e11fdf 100644
--- a/components/resources/BUILD.gn
+++ b/components/resources/BUILD.gn
@@ -176,7 +176,10 @@
       "android/webxr_resource_id.h",
     ]
 
-    public_deps = [ "//device/vr/buildflags" ]
+    public_deps = [
+      "//build:branding_buildflags",
+      "//device/vr/buildflags",
+    ]
   }
 }
 
diff --git a/components/resources/android/autofill_resource_id.h b/components/resources/android/autofill_resource_id.h
index d9ec674..1e6e3e8c 100644
--- a/components/resources/android/autofill_resource_id.h
+++ b/components/resources/android/autofill_resource_id.h
@@ -12,6 +12,8 @@
 // NOLINT(build/header_guard)
 // no-include-guard-because-multiply-included
 
+#include "build/branding_buildflags.h"
+
 // LINK_RESOURCE_ID is used for IDs that come from a .grd file.
 #ifndef LINK_RESOURCE_ID
 #error "LINK_RESOURCE_ID should be defined before including this file"
@@ -56,8 +58,21 @@
 LINK_RESOURCE_ID(IDR_AUTOFILL_CC_VISA_OLD, R.drawable.visa_card)
 LINK_RESOURCE_ID(IDR_AUTOFILL_CC_VISA, R.drawable.visa_card)
 LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY, R.drawable.google_pay)
-// TODO(crbug.com/438784697): update the resource id once the internal assets
-// access is added.
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_AFFIRM, R.drawable.googlepay_affirm)
+LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_AFFIRM_DARK,
+                 R.drawable.googlepay_affirm_dark)
+LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_AFTERPAY,
+                 R.drawable.googlepay_afterpay)
+LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_AFTERPAY_DARK,
+                 R.drawable.googlepay_afterpay_dark)
+LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_KLARNA, R.drawable.googlepay_klarna)
+LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_KLARNA_DARK,
+                 R.drawable.googlepay_klarna_dark)
+LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_ZIP, R.drawable.googlepay_zip)
+LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_ZIP_DARK,
+                 R.drawable.googlepay_zip_dark)
+#else
 LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_AFFIRM, R.drawable.bnpl_icon_generic)
 LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_AFFIRM_DARK,
                  R.drawable.bnpl_icon_generic)
@@ -69,6 +84,7 @@
                  R.drawable.bnpl_icon_generic)
 LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_ZIP, R.drawable.bnpl_icon_generic)
 LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_ZIP_DARK, R.drawable.bnpl_icon_generic)
+#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 LINK_RESOURCE_ID(IDR_AUTOFILL_KLARNA_LINKED, R.drawable.klarna_linked)
 LINK_RESOURCE_ID(IDR_AUTOFILL_KLARNA_UNLINKED, R.drawable.klarna_unlinked)
 LINK_RESOURCE_ID(IDR_AUTOFILL_METADATA_BNPL_GENERIC,
diff --git a/components/search_engines/search_engine_choice/search_engine_choice_service.cc b/components/search_engines/search_engine_choice/search_engine_choice_service.cc
index 5ba432e..b1455864 100644
--- a/components/search_engines/search_engine_choice/search_engine_choice_service.cc
+++ b/components/search_engines/search_engine_choice/search_engine_choice_service.cc
@@ -17,6 +17,7 @@
 #include "base/json/json_reader.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/metrics/puma_histogram_functions.h"
 #include "base/metrics/user_metrics.h"
 #include "base/no_destructor.h"
 #include "base/strings/string_util.h"
@@ -291,6 +292,9 @@
 
   base::UmaHistogramEnumeration(
       kSearchEngineChoiceScreenProfileInitConditionsHistogram, condition);
+  base::PumaHistogramEnumeration(
+      base::PumaType::kRc,
+      kPumaSearchChoiceScreenProfileInitConditionsHistogram, condition);
 }
 
 bool IsChoiceImported(const ChoiceCompletionMetadata& completion_metadata,
@@ -627,6 +631,9 @@
 
   base::UmaHistogramEnumeration(
       kSearchEngineChoiceScreenNavigationConditionsHistogram, condition);
+  base::PumaHistogramEnumeration(
+      base::PumaType::kRc, kPumaSearchChoiceScreenNavigationConditionsHistogram,
+      condition);
 
   regional_capabilities::RecordTriggeringFunnelStageDetails(condition);
   regional_capabilities::RecordFunnelStage(ToFunnelStage(condition));
@@ -643,6 +650,8 @@
 
   base::UmaHistogramEnumeration(kSearchEngineChoiceScreenEventsHistogram,
                                 event);
+  base::PumaHistogramEnumeration(base::PumaType::kRc,
+                                 kPumaSearchChoiceScreenEventsHistogram, event);
 
   if (event == SearchEngineChoiceScreenEvents::kChoiceScreenWasDisplayed ||
       event == SearchEngineChoiceScreenEvents::kFreChoiceScreenWasDisplayed ||
diff --git a/components/search_engines/search_engine_choice/search_engine_choice_service_unittest.cc b/components/search_engines/search_engine_choice/search_engine_choice_service_unittest.cc
index bd65355..16fde7f 100644
--- a/components/search_engines/search_engine_choice/search_engine_choice_service_unittest.cc
+++ b/components/search_engines/search_engine_choice/search_engine_choice_service_unittest.cc
@@ -166,8 +166,16 @@
       SearchEngineType::SEARCH_ENGINE_GOOGLE, 0);
   histogram_tester_.ExpectUniqueSample(
       search_engines::
+          kPumaSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
+      SearchEngineType::SEARCH_ENGINE_GOOGLE, 0);
+  histogram_tester_.ExpectUniqueSample(
+      search_engines::
           kSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
       SearchEngineType::SEARCH_ENGINE_GOOGLE, 0);
+  histogram_tester_.ExpectUniqueSample(
+      search_engines::
+          kPumaSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
+      SearchEngineType::SEARCH_ENGINE_GOOGLE, 0);
   EXPECT_FALSE(pref_service()->HasPrefPath(
       prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp));
   EXPECT_FALSE(pref_service()->HasPrefPath(
@@ -188,8 +196,16 @@
       SearchEngineType::SEARCH_ENGINE_GOOGLE, 1);
   histogram_tester_.ExpectUniqueSample(
       search_engines::
+          kPumaSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
+      SearchEngineType::SEARCH_ENGINE_GOOGLE, 1);
+  histogram_tester_.ExpectUniqueSample(
+      search_engines::
           kSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
       SearchEngineType::SEARCH_ENGINE_GOOGLE, 1);
+  histogram_tester_.ExpectUniqueSample(
+      search_engines::
+          kPumaSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
+      SearchEngineType::SEARCH_ENGINE_GOOGLE, 1);
 
   EXPECT_NEAR(pref_service()->GetInt64(
                   prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp),
@@ -218,8 +234,16 @@
       SearchEngineType::SEARCH_ENGINE_GOOGLE, 1);
   histogram_tester_.ExpectUniqueSample(
       search_engines::
+          kPumaSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
+      SearchEngineType::SEARCH_ENGINE_GOOGLE, 1);
+  histogram_tester_.ExpectUniqueSample(
+      search_engines::
           kSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
       SearchEngineType::SEARCH_ENGINE_GOOGLE, 1);
+  histogram_tester_.ExpectUniqueSample(
+      search_engines::
+          kPumaSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
+      SearchEngineType::SEARCH_ENGINE_GOOGLE, 1);
 }
 
 TEST_F(SearchEngineChoiceServiceTest, RecordChoiceMade_ByLocation_Waffle) {
@@ -261,10 +285,18 @@
         search_engines::
             kSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
         SearchEngineType::SEARCH_ENGINE_GOOGLE, expected_v1_records);
+    histogram_tester_.ExpectBucketCount(
+        search_engines::
+            kPumaSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
+        SearchEngineType::SEARCH_ENGINE_GOOGLE, expected_v1_records);
     histogram_tester_.ExpectUniqueSample(
         search_engines::
             kSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
         SearchEngineType::SEARCH_ENGINE_GOOGLE, expected_v2_records);
+    histogram_tester_.ExpectBucketCount(
+        search_engines::
+            kPumaSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
+        SearchEngineType::SEARCH_ENGINE_GOOGLE, expected_v2_records);
     WipeSearchEngineChoicePrefs(*pref_service(),
                                 SearchEngineChoiceWipeReason::kCommandLineFlag);
   }
@@ -357,10 +389,18 @@
   histogram_tester_.ExpectBucketCount(
       search_engines::kSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
       SearchEngineType::SEARCH_ENGINE_OTHER, 1);
+  histogram_tester_.ExpectBucketCount(
+      search_engines::
+          kPumaSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
+      SearchEngineType::SEARCH_ENGINE_OTHER, 1);
   histogram_tester_.ExpectUniqueSample(
       search_engines::
           kSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
       SearchEngineType::SEARCH_ENGINE_OTHER, 1);
+  histogram_tester_.ExpectUniqueSample(
+      search_engines::
+          kPumaSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
+      SearchEngineType::SEARCH_ENGINE_OTHER, 1);
 
   EXPECT_NEAR(pref_service()->GetInt64(
                   prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp),
@@ -395,10 +435,18 @@
   histogram_tester_.ExpectBucketCount(
       search_engines::kSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
       SearchEngineType::SEARCH_ENGINE_OTHER, 1);
+  histogram_tester_.ExpectBucketCount(
+      search_engines::
+          kPumaSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
+      SearchEngineType::SEARCH_ENGINE_OTHER, 1);
   histogram_tester_.ExpectUniqueSample(
       search_engines::
           kSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
       SearchEngineType::SEARCH_ENGINE_OTHER, 1);
+  histogram_tester_.ExpectUniqueSample(
+      search_engines::
+          kPumaSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
+      SearchEngineType::SEARCH_ENGINE_OTHER, 1);
 
   EXPECT_NEAR(pref_service()->GetInt64(
                   prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp),
@@ -429,6 +477,8 @@
 
   histogram_tester.ExpectUniqueSample(
       kSearchEngineChoiceScreenSelectedEngineIndexHistogram, 2, 1);
+  histogram_tester.ExpectUniqueSample(
+      kPumaSearchChoiceScreenSelectedEngineIndexHistogram, 2, 1);
   histogram_tester.ExpectBucketCount(
       kSearchEngineChoiceScreenShowedEngineAtCountryMismatchHistogram, false,
       1);
@@ -479,6 +529,8 @@
 
   histogram_tester.ExpectUniqueSample(
       kSearchEngineChoiceScreenSelectedEngineIndexHistogram, 2, 1);
+  histogram_tester.ExpectUniqueSample(
+      kPumaSearchChoiceScreenSelectedEngineIndexHistogram, 2, 1);
   histogram_tester.ExpectBucketCount(
       kSearchEngineChoiceScreenShowedEngineAtCountryMismatchHistogram, false,
       1);
@@ -530,6 +582,8 @@
   histogram_tester.ExpectTotalCount(
       kSearchEngineChoiceScreenSelectedEngineIndexHistogram, 0);
   histogram_tester.ExpectTotalCount(
+      kPumaSearchChoiceScreenSelectedEngineIndexHistogram, 0);
+  histogram_tester.ExpectTotalCount(
       kSearchEngineChoiceScreenShowedEngineAtCountryMismatchHistogram, 0);
 
   // The choice is coming from a non-eea country and won't be logged, don't
@@ -553,6 +607,8 @@
   histogram_tester.ExpectTotalCount(
       kSearchEngineChoiceScreenSelectedEngineIndexHistogram, 0);
   histogram_tester.ExpectTotalCount(
+      kPumaSearchChoiceScreenSelectedEngineIndexHistogram, 0);
+  histogram_tester.ExpectTotalCount(
       kSearchEngineChoiceScreenShowedEngineAtCountryMismatchHistogram, 0);
 
   // The choice is coming from a non-eea country and won't be logged, don't
@@ -583,6 +639,8 @@
   histogram_tester.ExpectBucketCount(
       kSearchEngineChoiceScreenSelectedEngineIndexHistogram, 0, 1);
   histogram_tester.ExpectBucketCount(
+      kPumaSearchChoiceScreenSelectedEngineIndexHistogram, 0, 1);
+  histogram_tester.ExpectBucketCount(
       kSearchEngineChoiceScreenShowedEngineAtCountryMismatchHistogram, true, 1);
 
   // None of the above should have logged the full list of indices.
@@ -651,6 +709,8 @@
   histogram_tester.ExpectTotalCount(
       kSearchEngineChoiceScreenSelectedEngineIndexHistogram, 0);
   histogram_tester.ExpectTotalCount(
+      kPumaSearchChoiceScreenSelectedEngineIndexHistogram, 0);
+  histogram_tester.ExpectTotalCount(
       kSearchEngineChoiceScreenShowedEngineAtCountryMismatchHistogram, 0);
 
   // The choice screen state should now be cleared.
@@ -693,6 +753,8 @@
   histogram_tester.ExpectTotalCount(
       kSearchEngineChoiceScreenSelectedEngineIndexHistogram, 0);
   histogram_tester.ExpectTotalCount(
+      kPumaSearchChoiceScreenSelectedEngineIndexHistogram, 0);
+  histogram_tester.ExpectTotalCount(
       kSearchEngineChoiceScreenShowedEngineAtCountryMismatchHistogram, 0);
 
   // The choice screen state should stay around.
@@ -1327,8 +1389,14 @@
       search_engines::kSearchEngineChoiceScreenProfileInitConditionsHistogram,
       expected_eligibility_condition, 1);
   histogram_tester_.ExpectUniqueSample(
+      search_engines::kPumaSearchChoiceScreenProfileInitConditionsHistogram,
+      expected_eligibility_condition, 1);
+  histogram_tester_.ExpectUniqueSample(
       "RegionalCapabilities.FunnelStage.Eligibility",
       expected_eligibility_condition, 1);
+  histogram_tester_.ExpectUniqueSample(
+      "PUMA.RegionalCapabilities.FunnelStage.Eligibility",
+      expected_eligibility_condition, 1);
   if (GetParam().restore_detected_in_current_session &&
       GetParam().is_feature_enabled) {
     histogram_tester_.ExpectUniqueSample(
@@ -1343,8 +1411,14 @@
       search_engines::kSearchEngineChoiceScreenNavigationConditionsHistogram,
       expected_eligibility_condition, 1);
   histogram_tester_.ExpectUniqueSample(
+      search_engines::kPumaSearchChoiceScreenNavigationConditionsHistogram,
+      expected_eligibility_condition, 1);
+  histogram_tester_.ExpectUniqueSample(
       "RegionalCapabilities.FunnelStage.Triggering",
       expected_eligibility_condition, 1);
+  histogram_tester_.ExpectUniqueSample(
+      "PUMA.RegionalCapabilities.FunnelStage.Triggering",
+      expected_eligibility_condition, 1);
   if (GetParam().restore_detected_in_current_session &&
       GetParam().is_feature_enabled) {
     histogram_tester_.ExpectUniqueSample(
@@ -1597,6 +1671,9 @@
     CheckHistogramExpectation(scoped_histogram_tester,
                               "RegionalCapabilities.FunnelStage.Reported",
                               GetParam().expected_if_static);
+    CheckHistogramExpectation(scoped_histogram_tester,
+                              "PUMA.RegionalCapabilities.FunnelStage.Reported",
+                              GetParam().expected_if_static);
   }
 
   {
@@ -1606,6 +1683,9 @@
     CheckHistogramExpectation(scoped_histogram_tester,
                               "RegionalCapabilities.FunnelStage.Reported",
                               GetParam().expected_if_dynamic);
+    CheckHistogramExpectation(scoped_histogram_tester,
+                              "PUMA.RegionalCapabilities.FunnelStage.Reported",
+                              GetParam().expected_if_dynamic);
   }
 }
 
diff --git a/components/search_engines/search_engine_choice/search_engine_choice_utils.cc b/components/search_engines/search_engine_choice/search_engine_choice_utils.cc
index c86f023aec..859a938 100644
--- a/components/search_engines/search_engine_choice/search_engine_choice_utils.cc
+++ b/components/search_engines/search_engine_choice/search_engine_choice_utils.cc
@@ -17,6 +17,7 @@
 #include "base/containers/to_vector.h"
 #include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/metrics/puma_histogram_functions.h"
 #include "base/metrics/user_metrics.h"
 #include "base/not_fatal_until.h"
 #include "base/strings/stringprintf.h"
@@ -186,10 +187,18 @@
   base::UmaHistogramEnumeration(
       kSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram, engine_type,
       SEARCH_ENGINE_MAX);
+  base::PumaHistogramEnumeration(
+      base::PumaType::kRc,
+      kPumaSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram,
+      engine_type, SEARCH_ENGINE_MAX);
   if (choice_location == ChoiceMadeLocation::kChoiceScreen) {
     base::UmaHistogramEnumeration(
         kSearchEngineChoiceScreenDefaultSearchEngineType2Histogram, engine_type,
         SEARCH_ENGINE_MAX);
+    base::PumaHistogramEnumeration(
+        base::PumaType::kRc,
+        kPumaSearchEngineChoiceScreenDefaultSearchEngineType2Histogram,
+        engine_type, SEARCH_ENGINE_MAX);
   }
 }
 
@@ -198,6 +207,10 @@
       kSearchEngineChoiceScreenSelectedEngineIndexHistogram,
       selected_engine_index,
       TemplateURLPrepopulateData::kMaxEeaPrepopulatedEngines);
+  base::PumaHistogramExactLinear(
+      base::PumaType::kRc, kPumaSearchChoiceScreenSelectedEngineIndexHistogram,
+      selected_engine_index,
+      TemplateURLPrepopulateData::kMaxEeaPrepopulatedEngines);
 }
 
 void RecordChoiceScreenPositionsCountryMismatch(bool has_mismatch) {
diff --git a/components/search_engines/search_engine_choice/search_engine_choice_utils.h b/components/search_engines/search_engine_choice/search_engine_choice_utils.h
index fd3f0f4..b0218f69 100644
--- a/components/search_engines/search_engine_choice/search_engine_choice_utils.h
+++ b/components/search_engines/search_engine_choice/search_engine_choice_utils.h
@@ -36,24 +36,38 @@
 inline constexpr char
     kSearchEngineChoiceScreenProfileInitConditionsHistogram[] =
         "Search.ChoiceScreenProfileInitConditions";
+inline constexpr char kPumaSearchChoiceScreenProfileInitConditionsHistogram[] =
+    "PUMA.RegionalCapabilities.Search.ChoiceScreenProfileInitConditions";
 inline constexpr char kSearchEngineChoiceScreenNavigationConditionsHistogram[] =
     "Search.ChoiceScreenNavigationConditions";
+inline constexpr char kPumaSearchChoiceScreenNavigationConditionsHistogram[] =
+    "PUMA.RegionalCapabilities.Search.ChoiceScreenNavigationConditions";
 inline constexpr char kChoiceScreenProfileInitConditionsPostRestoreHistogram[] =
     "Search.ChoiceScreenProfileInitConditions.PostRestore";
 inline constexpr char kChoiceScreenNavigationConditionsPostRestoreHistogram[] =
     "Search.ChoiceScreenNavigationConditions.PostRestore";
 inline constexpr char kSearchEngineChoiceScreenEventsHistogram[] =
     "Search.ChoiceScreenEvents";
+inline constexpr char kPumaSearchChoiceScreenEventsHistogram[] =
+    "PUMA.RegionalCapabilities.Search.ChoiceScreenEvents";
 inline constexpr char kChoiceScreenEventsPostRestoreHistogram[] =
     "Search.ChoiceScreenEvents.PostRestore";
 inline constexpr char
     kSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram[] =
         "Search.ChoiceScreenDefaultSearchEngineType";
 inline constexpr char
+    kPumaSearchEngineChoiceScreenDefaultSearchEngineTypeHistogram[] =
+        "PUMA.RegionalCapabilities.Search.ChoiceScreenDefaultSearchEngineType";
+inline constexpr char
     kSearchEngineChoiceScreenDefaultSearchEngineType2Histogram[] =
         "Search.ChoiceScreenDefaultSearchEngineType2";
+inline constexpr char
+    kPumaSearchEngineChoiceScreenDefaultSearchEngineType2Histogram[] =
+        "PUMA.RegionalCapabilities.Search.ChoiceScreenDefaultSearchEngineType2";
 inline constexpr char kSearchEngineChoiceScreenSelectedEngineIndexHistogram[] =
     "Search.ChoiceScreenSelectedEngineIndex";
+inline constexpr char kPumaSearchChoiceScreenSelectedEngineIndexHistogram[] =
+    "PUMA.RegionalCapabilities.Search.ChoiceScreenSelectedEngineIndex";
 inline constexpr char
     kSearchEngineChoiceScreenShowedEngineAtHistogramPattern[] =
         "Search.ChoiceScreenShowedEngineAt.Index%d";
diff --git a/components/services/filesystem/directory_impl.cc b/components/services/filesystem/directory_impl.cc
index c7aca40..26e35ea 100644
--- a/components/services/filesystem/directory_impl.cc
+++ b/components/services/filesystem/directory_impl.cc
@@ -292,12 +292,16 @@
   }
 
   std::vector<uint8_t> contents;
-  const int kBufferSize = 1 << 16;
-  auto buf = base::HeapArray<char>::Uninit(kBufferSize);
-  int len;
-  while ((len = UNSAFE_TODO(
-              base_file.ReadAtCurrentPos(buf.data(), kBufferSize))) > 0) {
-    contents.insert(contents.end(), buf.data(), UNSAFE_TODO(buf.data() + len));
+  constexpr int kBufferSize = 1 << 16;
+  auto buf = base::HeapArray<uint8_t>::Uninit(kBufferSize);
+  while (true) {
+    std::optional<size_t> bytes_read =
+        base_file.ReadAtCurrentPos(buf.as_span());
+    if (bytes_read.value_or(0) == 0) {
+      break;
+    }
+    base::span<const uint8_t> bytes = buf.first(bytes_read.value());
+    contents.insert(contents.end(), bytes.begin(), bytes.end());
   }
 
   std::move(callback).Run(base::File::Error::FILE_OK, contents);
diff --git a/components/services/storage/dom_storage/async_dom_storage_database.cc b/components/services/storage/dom_storage/async_dom_storage_database.cc
index dda5535..038a18a 100644
--- a/components/services/storage/dom_storage/async_dom_storage_database.cc
+++ b/components/services/storage/dom_storage/async_dom_storage_database.cc
@@ -180,7 +180,10 @@
               }
 
               if (commit.clear_all_first) {
-                batch->DeletePrefixed(commit.prefix);
+                DbStatus status = batch->DeletePrefixed(commit.prefix);
+                if (!status.ok()) {
+                  return status;
+                }
               }
               for (const auto& entry : commit.entries_to_add) {
                 batch->Put(entry.key, entry.value);
@@ -189,8 +192,11 @@
                 batch->Delete(key);
               }
               if (commit.copy_to_prefix) {
-                batch->CopyPrefixed(commit.prefix,
-                                    commit.copy_to_prefix.value());
+                DbStatus status = batch->CopyPrefixed(
+                    commit.prefix, commit.copy_to_prefix.value());
+                if (!status.ok()) {
+                  return status;
+                }
               }
             }
             return batch->Commit();
diff --git a/components/services/storage/dom_storage/leveldb/dom_storage_batch_operation_leveldb.h b/components/services/storage/dom_storage/leveldb/dom_storage_batch_operation_leveldb.h
index 78848eb..582a01f0 100644
--- a/components/services/storage/dom_storage/leveldb/dom_storage_batch_operation_leveldb.h
+++ b/components/services/storage/dom_storage/leveldb/dom_storage_batch_operation_leveldb.h
@@ -32,9 +32,9 @@
 
   void Put(KeyView key, ValueView value);
   void Delete(KeyView key);
-  DbStatus DeletePrefixed(KeyView prefix);
-  DbStatus CopyPrefixed(KeyView prefix, KeyView new_prefix);
-  DbStatus Commit();
+  [[nodiscard]] DbStatus DeletePrefixed(KeyView prefix);
+  [[nodiscard]] DbStatus CopyPrefixed(KeyView prefix, KeyView new_prefix);
+  [[nodiscard]] DbStatus Commit();
   size_t ApproximateSizeForMetrics() const;
 
  private:
diff --git a/components/services/storage/dom_storage/leveldb/local_storage_leveldb.cc b/components/services/storage/dom_storage/leveldb/local_storage_leveldb.cc
index 5d49328..6d8c59d 100644
--- a/components/services/storage/dom_storage/leveldb/local_storage_leveldb.cc
+++ b/components/services/storage/dom_storage/leveldb/local_storage_leveldb.cc
@@ -290,7 +290,10 @@
 
   for (const blink::StorageKey& storage_key : storage_keys) {
     // Erase all map key/value pairs.
-    batch->DeletePrefixed(GetMapPrefix(storage_key));
+    DbStatus status = batch->DeletePrefixed(GetMapPrefix(storage_key));
+    if (!status.ok()) {
+      return status;
+    }
 
     // Erase the "METAACCESS:" entry.
     batch->Delete(CreateAccessMetaDataKey(storage_key));
diff --git a/components/services/storage/dom_storage/session_storage_impl_unittest.cc b/components/services/storage/dom_storage/session_storage_impl_unittest.cc
index 6651035e..56fc01f 100644
--- a/components/services/storage/dom_storage/session_storage_impl_unittest.cc
+++ b/components/services/storage/dom_storage/session_storage_impl_unittest.cc
@@ -920,7 +920,7 @@
       base::BindOnce([](DomStorageDatabaseLevelDB& db) {
         std::unique_ptr<DomStorageBatchOperationLevelDB> batch =
             db.CreateBatchOperation();
-        batch->DeletePrefixed(StringViewToUint8Vector("map"));
+        EXPECT_TRUE(batch->DeletePrefixed(StringViewToUint8Vector("map")).ok());
         EXPECT_TRUE(batch->Commit().ok());
         return 0;
       }),
diff --git a/components/services/storage/dom_storage/session_storage_metadata.cc b/components/services/storage/dom_storage/session_storage_metadata.cc
index 9a5c1c3..2954bed 100644
--- a/components/services/storage/dom_storage/session_storage_metadata.cc
+++ b/components/services/storage/dom_storage/session_storage_metadata.cc
@@ -189,7 +189,7 @@
          DomStorageBatchOperationLevelDB& batch,
          const DomStorageDatabaseLevelDB& db) {
         for (const auto& prefix : prefixes_to_delete)
-          batch.DeletePrefixed(prefix);
+          std::ignore = batch.DeletePrefixed(prefix);
       },
       std::move(prefixes_to_delete)));
 }
@@ -225,7 +225,7 @@
          const DomStorageDatabaseLevelDB& db) {
         batch.Delete(area_key);
         for (const auto& prefix : prefixes_to_delete)
-          batch.DeletePrefixed(prefix);
+          std::ignore = batch.DeletePrefixed(prefix);
       },
       area_key, std::move(prefixes_to_delete)));
 }
diff --git a/components/services/storage/indexed_db/scopes/leveldb_scopes.cc b/components/services/storage/indexed_db/scopes/leveldb_scopes.cc
index b5e7819..6e7e625 100644
--- a/components/services/storage/indexed_db/scopes/leveldb_scopes.cc
+++ b/components/services/storage/indexed_db/scopes/leveldb_scopes.cc
@@ -10,6 +10,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
 #include "base/location.h"
 #include "base/memory/ptr_util.h"
@@ -237,7 +238,7 @@
             std::move(startup_scopes_to_clean_), level_db_,
             metadata_key_prefix_, max_write_batch_size_bytes_),
         base::BindOnce(&LevelDBScopes::OnCleanupTaskResult,
-                       weak_factory_.GetWeakPtr(), base::OnceClosure()));
+                       weak_factory_.GetWeakPtr()));
   }
 }
 
@@ -271,9 +272,18 @@
         max_write_batch_size_bytes_);
     cleanup_runner_->PostTaskAndReplyWithResult(
         FROM_HERE, base::BindOnce(&CleanupScopeTask::Run, std::move(task)),
-        base::BindOnce(&LevelDBScopes::OnCleanupTaskResult,
-                       weak_factory_.GetWeakPtr(),
-                       std::move(on_cleanup_complete)));
+        base::BindOnce(
+            [](base::OnceClosure on_cleanup_complete,
+               base::OnceCallback<void(leveldb::Status)> callback,
+               leveldb::Status result) {
+              if (on_cleanup_complete) {
+                std::move(on_cleanup_complete).Run();
+              }
+              std::move(callback).Run(result);
+            },
+            std::move(on_cleanup_complete),
+            base::BindOnce(&LevelDBScopes::OnCleanupTaskResult,
+                           weak_factory_.GetWeakPtr())));
   }
   return status;
 }
@@ -300,18 +310,14 @@
   cleanup_runner_->PostTaskAndReplyWithResult(
       FROM_HERE, base::BindOnce(&CleanupScopeTask::Run, std::move(task)),
       base::BindOnce(&LevelDBScopes::OnCleanupTaskResult,
-                     weak_factory_.GetWeakPtr(), base::OnceClosure()));
+                     weak_factory_.GetWeakPtr()));
 }
 
-void LevelDBScopes::OnCleanupTaskResult(base::OnceClosure on_complete,
-                                        leveldb::Status result) {
+void LevelDBScopes::OnCleanupTaskResult(leveldb::Status result) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!result.ok()) [[unlikely]] {
     tear_down_callback_.Run(result);
   }
-  if (on_complete) {
-    std::move(on_complete).Run();
-  }
 }
 
 }  // namespace content::indexed_db
diff --git a/components/services/storage/indexed_db/scopes/leveldb_scopes.h b/components/services/storage/indexed_db/scopes/leveldb_scopes.h
index eb43258..d6d7012 100644
--- a/components/services/storage/indexed_db/scopes/leveldb_scopes.h
+++ b/components/services/storage/indexed_db/scopes/leveldb_scopes.h
@@ -81,8 +81,7 @@
 
   void Rollback(int64_t scope_id, std::vector<PartitionedLock> locks);
 
-  void OnCleanupTaskResult(base::OnceClosure on_complete,
-                           leveldb::Status result);
+  void OnCleanupTaskResult(leveldb::Status result);
 
   SEQUENCE_CHECKER(sequence_checker_);
   const std::vector<uint8_t> metadata_key_prefix_;
diff --git a/components/stylus_handwriting/win/OWNERS b/components/stylus_handwriting/win/OWNERS
index ffe052c..47dc959 100644
--- a/components/stylus_handwriting/win/OWNERS
+++ b/components/stylus_handwriting/win/OWNERS
@@ -1,5 +1,2 @@
 file://components/stylus_handwriting/OWNERS
-
-Adam.Ettenberger@microsoft.com
-arakeri@microsoft.com
-yshalivskyy@microsoft.com
+arakeri@microsoft.com
\ No newline at end of file
diff --git a/components/variations/service/BUILD.gn b/components/variations/service/BUILD.gn
index fda6153e..3bfdd256 100644
--- a/components/variations/service/BUILD.gn
+++ b/components/variations/service/BUILD.gn
@@ -44,6 +44,8 @@
     "ui_string_overrider.h",
     "variations_field_trial_creator.cc",
     "variations_field_trial_creator.h",
+    "variations_network_clock.cc",
+    "variations_network_clock.h",
     "variations_service.cc",
     "variations_service.h",
     "variations_service_client.cc",
@@ -63,6 +65,7 @@
     "//components/language/core/browser",
     "//components/metrics",
     "//components/network_time",
+    "//components/network_time/time_tracker",
     "//components/pref_registry",
     "//components/prefs",
     "//components/sync/service",
@@ -85,6 +88,7 @@
     "safe_seed_manager_unittest.cc",
     "ui_string_overrider_unittest.cc",
     "variations_field_trial_creator_unittest.cc",
+    "variations_network_clock_unittest.cc",
     "variations_service_unittest.cc",
     "variations_service_utils_unittest.cc",
   ]
@@ -97,6 +101,7 @@
     "//build:branding_buildflags",
     "//components/metrics",
     "//components/metrics:test_support",
+    "//components/network_time",
     "//components/prefs:test_support",
     "//components/sync:test_support",
     "//components/sync_preferences:test_support",
diff --git a/components/variations/service/variations_network_clock.cc b/components/variations/service/variations_network_clock.cc
new file mode 100644
index 0000000..c7de30a
--- /dev/null
+++ b/components/variations/service/variations_network_clock.cc
@@ -0,0 +1,74 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/service/variations_network_clock.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "base/time/time.h"
+#include "components/network_time/network_time_tracker.h"
+#include "components/network_time/time_tracker/time_tracker.h"
+
+namespace variations {
+
+namespace {
+
+// The source of time used by the VariationsNetworkClock. These values are
+// persisted to logs. Entries should not be renumbered and numeric values should
+// never be reused. Keep these values in sync with the VariationsTimeSource
+// enum in //tools/metrics/histograms/metadata/variations/enums.xml.
+enum class TimeSource {
+  kUnknown = 0,
+  kLocal = 1,
+  kNetwork = 2,
+  kMaxValue = kNetwork,
+};
+
+// Generates a histogram sample indicating whether the VariationsNetworkClock is
+// using network time or falling back to local time.
+void LogTimeSource(TimeSource time_source) {
+  base::UmaHistogramEnumeration("Variations.Headers.TimeSource", time_source);
+}
+
+}  // namespace
+
+VariationsNetworkClock::VariationsNetworkClock(
+    network_time::NetworkTimeTracker* tracker)
+    : network_time::NetworkTimeTracker::NetworkTimeObserver(tracker) {
+  network_time::TimeTracker::TimeTrackerState state;
+  if (tracker->GetTrackerState(&state)) {
+    UpdateTimeTracker(state);
+  }
+}
+
+VariationsNetworkClock::~VariationsNetworkClock() = default;
+
+base::Time VariationsNetworkClock::Now() const {
+  const base::Time local_time = base::Time::Now();
+  const base::TimeTicks local_ticks = base::TimeTicks::Now();
+  base::Time estimated_time;
+
+  base::AutoLock lock(lock_);
+  if (time_tracker_.has_value() &&
+      time_tracker_->GetTime(local_time, local_ticks, &estimated_time,
+                             /*uncertainty=*/nullptr)) {
+    LogTimeSource(TimeSource::kNetwork);
+    return estimated_time;
+  }
+  LogTimeSource(TimeSource::kLocal);
+  return local_time;
+}
+
+void VariationsNetworkClock::OnNetworkTimeChanged(
+    network_time::TimeTracker::TimeTrackerState state) {
+  UpdateTimeTracker(state);
+}
+
+void VariationsNetworkClock::UpdateTimeTracker(
+    const network_time::TimeTracker::TimeTrackerState& state) {
+  base::AutoLock lock(lock_);
+  time_tracker_.emplace(state.system_time, state.system_ticks, state.known_time,
+                        state.uncertainty);
+}
+
+}  // namespace variations
diff --git a/components/variations/service/variations_network_clock.h b/components/variations/service/variations_network_clock.h
new file mode 100644
index 0000000..6262ebe
--- /dev/null
+++ b/components/variations/service/variations_network_clock.h
@@ -0,0 +1,54 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_NETWORK_CLOCK_H_
+#define COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_NETWORK_CLOCK_H_
+
+#include <optional>
+
+#include "base/memory/raw_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/time/clock.h"
+#include "base/time/time.h"
+#include "components/network_time/network_time_tracker.h"
+#include "components/network_time/time_tracker/time_tracker.h"
+
+namespace variations {
+
+// A thread-safe clock implementation that tracks network time.
+class VariationsNetworkClock
+    : public base::Clock,
+      public network_time::NetworkTimeTracker::NetworkTimeObserver {
+ public:
+  // `network_time_tracker` must be non-null. The VariationsNetworkClock and
+  // NetworkTimeTracker correctly handle either being destroyed first.
+  explicit VariationsNetworkClock(
+      network_time::NetworkTimeTracker* network_time_tracker);
+
+  ~VariationsNetworkClock() override;
+
+  VariationsNetworkClock(const VariationsNetworkClock&) = delete;
+  VariationsNetworkClock& operator=(const VariationsNetworkClock&) = delete;
+
+  // base::Clock:
+  base::Time Now() const override;
+
+  // network_time::NetworkTimeTracker::NetworkTimeObserver:
+  void OnNetworkTimeChanged(
+      network_time::TimeTracker::TimeTrackerState state) override;
+
+ private:
+  // Updates the `time_tracker_` with the given state.
+  void UpdateTimeTracker(
+      const network_time::TimeTracker::TimeTrackerState& state);
+
+  mutable base::Lock lock_;
+
+  // Tracks "known" time vs timeticks to estimate network time.
+  std::optional<network_time::TimeTracker> time_tracker_ GUARDED_BY(lock_);
+};
+
+}  // namespace variations
+
+#endif  // COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_NETWORK_CLOCK_H_
diff --git a/components/variations/service/variations_network_clock_unittest.cc b/components/variations/service/variations_network_clock_unittest.cc
new file mode 100644
index 0000000..c3fd0c8
--- /dev/null
+++ b/components/variations/service/variations_network_clock_unittest.cc
@@ -0,0 +1,204 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/service/variations_network_clock.h"
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include "base/memory/raw_ptr.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/time/default_clock.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/time.h"
+#include "base/time/time_override.h"
+#include "components/network_time/network_time_tracker.h"
+#include "components/prefs/testing_pref_service.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace variations {
+namespace {
+
+using ::network_time::NetworkTimeTracker;
+
+// The time source histogram name and bucket values.
+constexpr std::string_view kTimeSourceHistogramName =
+    "Variations.Headers.TimeSource";
+constexpr int kLocalBucket = 1;
+constexpr int kNetworkBucket = 2;
+
+class VariationsNetworkClockTest : public ::testing::Test {
+ public:
+  VariationsNetworkClockTest() {
+    CHECK(test_fixture_ == nullptr);
+    test_fixture_ = this;
+  }
+
+  ~VariationsNetworkClockTest() override {
+    CHECK(test_fixture_ == this);
+    test_fixture_ = nullptr;
+  }
+
+  void SetUp() override {
+    NetworkTimeTracker::RegisterPrefs(pref_service_.registry());
+
+    // Override the system clock(s) and thread ticks.
+    time_clock_overrides_ =
+        std::make_unique<base::subtle::ScopedTimeClockOverrides>(
+            &VariationsNetworkClockTest::Now,
+            &VariationsNetworkClockTest::NowTicks,
+            &VariationsNetworkClockTest::NowThreadTicks);
+
+    // Create a NetworkTimeTracker with a `FETCHES_ON_DEMAND_ONLY` fetch
+    // behavior, so that we can manually simulate network time updates.
+    network_time_tracker_ = std::make_unique<network_time::NetworkTimeTracker>(
+        std::make_unique<base::DefaultClock>(),
+        std::make_unique<base::DefaultTickClock>(), &pref_service_,
+        /*url_loader_factory=*/nullptr,
+        NetworkTimeTracker::FETCHES_ON_DEMAND_ONLY);
+
+    // Create a VariationsNetworkClock using the NetworkTimeTracker.
+    variations_network_clock_ =
+        std::make_unique<VariationsNetworkClock>(network_time_tracker_.get());
+  }
+
+  // Simulates an update to the network time.
+  void UpdateNetworkTime() {
+    // Can not be smaller than 15, it's the NowFromSystemTime() resolution.
+    constexpr base::TimeDelta resolution_ = base::Milliseconds(17);
+    constexpr base::TimeDelta latency_ = base::Milliseconds(50);
+
+    network_time_tracker_->UpdateNetworkTime(
+        SimulatedLocalTime() - (latency_ / 2), resolution_, latency_,
+        SimulatedLocalTimeTicks());
+  }
+
+  // Advances the simulated clock(s).
+  void AdvanceClock(base::TimeDelta delta) {
+    CHECK(delta >= base::TimeDelta());
+    offset_ += delta;
+  }
+
+  // Offsets the simulated wall-clock by the given delta, leaving the remaining
+  // clocks unchanged.
+  void DivergeClock(base::TimeDelta divergence) { divergence_ = divergence; }
+
+  // Returns the current estimated network time of `variations_network_clock_`.
+  base::Time EstimatedNetworkTime() const {
+    return variations_network_clock_->Now();
+  }
+
+  // Returns the current simulated local time. This is just an alias for
+  // base::Time::Now() for readability. In turn, base::Time::Now() has been
+  // overridden to call `VariationsNetworkClockTest::Now()`.
+  base::Time SimulatedLocalTime() const { return base::Time::Now(); }
+
+  // Returns the current simulated local time ticks. This is just an alias for
+  // base::TimeTicks::Now() for readability. In turn, base::TimeTicks::Now()
+  // has been overridden to call `VariationsNetworkClockTest::NowTicks()`.
+  base::TimeTicks SimulatedLocalTimeTicks() const {
+    return base::TimeTicks::Now();
+  }
+
+ protected:
+  // Override for `base::Time::Now()`.
+  static base::Time Now() {
+    return base::Time() + test_fixture_->offset_ + test_fixture_->divergence_;
+  }
+
+  // Override for `base::TimeTicks::Now()`.
+  static base::TimeTicks NowTicks() {
+    return base::TimeTicks() + test_fixture_->offset_;
+  }
+
+  // Override for `base::ThreadTicks::Now()`.
+  static base::ThreadTicks NowThreadTicks() {
+    return base::ThreadTicks() + test_fixture_->offset_;
+  }
+
+  // The test fixture instance. This is used to access the simulated clock
+  // offsets from the static methods above.
+  static VariationsNetworkClockTest* test_fixture_;
+
+  // The offset applied to all simulated clocks. Initialize to a non-zero value
+  // to ensure that the simulated clocks are all non-zero.
+  base::TimeDelta offset_{base::Days(365)};
+
+  // The divergence applied to the simulated wall-clock. Used to simulate clock
+  // sync loss.
+  base::TimeDelta divergence_;
+
+  // Remaining test state variables.
+  TestingPrefServiceSimple pref_service_;
+  base::HistogramTester histogram_tester_;
+  std::unique_ptr<base::subtle::ScopedTimeClockOverrides> time_clock_overrides_;
+  std::unique_ptr<NetworkTimeTracker> network_time_tracker_;
+  std::unique_ptr<VariationsNetworkClock> variations_network_clock_;
+};
+
+VariationsNetworkClockTest* VariationsNetworkClockTest::test_fixture_ = nullptr;
+
+TEST_F(VariationsNetworkClockTest, NetworkTimeUpdates) {
+  // Having not received yet any simulated network time updates, the estimated
+  // time should be equal to the simulated time (since it's falling back to the
+  // local clock). The histogram tracking the time source should have a single
+  // sample, indicating that local time was used.
+  auto estimated_now = EstimatedNetworkTime();
+  auto simulated_now = SimulatedLocalTime();
+  EXPECT_EQ(estimated_now, simulated_now);
+  histogram_tester_.ExpectBucketCount(kTimeSourceHistogramName,
+                                      /*sample=*/kLocalBucket,
+                                      /*expected_count=*/1);
+
+  // Simulate a network time update. Now the estimator will start using the
+  // simulated time for future estimates.
+  UpdateNetworkTime();
+
+  // The estimator should now be using the simulated network time, so the
+  // estimated time should be equal to the simulated time. The histogram should
+  // have a second sample, indicating that network time was used.
+  estimated_now = EstimatedNetworkTime();
+  simulated_now = SimulatedLocalTime();
+  EXPECT_EQ(estimated_now, simulated_now);
+  histogram_tester_.ExpectBucketCount(kTimeSourceHistogramName,
+                                      /*sample=*/kNetworkBucket,
+                                      /*expected_count=*/1);
+
+  // Advance the simulated clock(s). The simulated tick clock will be used to
+  // estimate the network time... so it should still match the simulated clock.
+  AdvanceClock(base::Minutes(10));
+  estimated_now = EstimatedNetworkTime();
+  simulated_now = SimulatedLocalTime();
+  EXPECT_EQ(estimated_now, simulated_now);
+  histogram_tester_.ExpectBucketCount(kTimeSourceHistogramName,
+                                      /*sample=*/kNetworkBucket,
+                                      /*expected_count=*/2);
+
+  // Simulate a clock divergence of one hour. The VariationsNetworkClock will
+  // fall back to local clock until it receives a new update.
+  DivergeClock(base::Hours(1));
+  estimated_now = EstimatedNetworkTime();
+  simulated_now = SimulatedLocalTime();
+  EXPECT_EQ(estimated_now, simulated_now);
+  histogram_tester_.ExpectBucketCount(kTimeSourceHistogramName,
+                                      /*sample=*/kLocalBucket,
+                                      /*expected_count=*/2);
+
+  // Update the network time. The local clock divergence is still one hour, but
+  // now that divergence is accounted for by the estimator.
+  UpdateNetworkTime();
+  AdvanceClock(base::Hours(2));
+  estimated_now = EstimatedNetworkTime();
+  simulated_now = SimulatedLocalTime();
+  EXPECT_EQ(estimated_now, simulated_now);
+  histogram_tester_.ExpectBucketCount(kTimeSourceHistogramName,
+                                      /*sample=*/kNetworkBucket,
+                                      /*expected_count=*/3);
+}
+
+}  // namespace
+}  // namespace variations
diff --git a/components/viz/service/transitions/transferable_resource_tracker_unittest.cc b/components/viz/service/transitions/transferable_resource_tracker_unittest.cc
index fd015dd..160a30c 100644
--- a/components/viz/service/transitions/transferable_resource_tracker_unittest.cc
+++ b/components/viz/service/transitions/transferable_resource_tracker_unittest.cc
@@ -75,14 +75,19 @@
 
   EXPECT_GE(resource2->resource.id, resource1->resource.id);
 
+  gpu::Mailbox mailbox1 = resource1->resource.mailbox();
   tracker.ReturnFrame(frame1);
-  EXPECT_FALSE(HasSharedImageForSoftwareResource(resource1->resource));
+  frame1 = TransferableResourceTracker::ResourceFrame();
+  EXPECT_FALSE(shared_image_interface()->CheckSharedImageExists(mailbox1));
 
-  tracker.RefResource(resource2->resource.id);
+  gpu::Mailbox mailbox2 = resource2->resource.mailbox();
+  ResourceId id2 = resource2->resource.id;
+  tracker.RefResource(id2);
   tracker.ReturnFrame(frame2);
-  EXPECT_TRUE(HasSharedImageForSoftwareResource(resource2->resource));
-  tracker.UnrefResource(resource2->resource.id, 1, gpu::SyncToken());
-  EXPECT_FALSE(HasSharedImageForSoftwareResource(resource2->resource));
+  frame2 = TransferableResourceTracker::ResourceFrame();
+  EXPECT_TRUE(shared_image_interface()->CheckSharedImageExists(mailbox2));
+  tracker.UnrefResource(id2, 1, gpu::SyncToken());
+  EXPECT_FALSE(shared_image_interface()->CheckSharedImageExists(mailbox2));
 }
 
 TEST_F(TransferableResourceTrackerTest, ExhaustedIdLoops) {
@@ -109,9 +114,10 @@
     frames.push_back(std::move(frame));
   }
   for (auto& frame : frames) {
+    gpu::Mailbox mailbox = frame.shared.at(0)->resource.mailbox();
     tracker.ReturnFrame(frame);
-    EXPECT_FALSE(
-        HasSharedImageForSoftwareResource(frame.shared.at(0)->resource));
+    frame = TransferableResourceTracker::ResourceFrame();
+    EXPECT_FALSE(shared_image_interface()->CheckSharedImageExists(mailbox));
   }
 }
 
@@ -166,9 +172,10 @@
     frames.push_back(std::move(frame));
   }
   for (auto& frame : frames) {
+    gpu::Mailbox mailbox = frame.shared.at(0)->resource.mailbox();
     tracker.ReturnFrame(frame);
-    EXPECT_FALSE(
-        HasSharedImageForSoftwareResource(frame.shared.at(0)->resource));
+    frame = TransferableResourceTracker::ResourceFrame();
+    EXPECT_FALSE(shared_image_interface()->CheckSharedImageExists(mailbox));
   }
 }
 
diff --git a/content/app/ios/appex/child_process.mm b/content/app/ios/appex/child_process.mm
index e4637a23..cb4ea52 100644
--- a/content/app/ios/appex/child_process.mm
+++ b/content/app/ios/appex/child_process.mm
@@ -4,19 +4,6 @@
 
 #import <Foundation/Foundation.h>
 
-#include "base/allocator/early_zone_registration_apple.h"
-
-@interface EarlyInitializationObject : NSObject
-@end
-
-@implementation EarlyInitializationObject
-+ (void)load {
-  // Perform malloc zone registration early. See
-  // https://developer.apple.com/documentation/objectivec/nsobject-swift.class/load()?language=objc#Discussion
-  partition_alloc::EarlyMallocZoneRegistration();
-}
-@end
-
 #define IOS_INIT_EXPORT __attribute__((visibility("default")))
 
 extern "C" void IOS_INIT_EXPORT ChildProcessStarted() {
diff --git a/content/browser/indexed_db/indexed_db_context_impl.cc b/content/browser/indexed_db/indexed_db_context_impl.cc
index e964e4e..6c12366 100644
--- a/content/browser/indexed_db/indexed_db_context_impl.cc
+++ b/content/browser/indexed_db/indexed_db_context_impl.cc
@@ -92,16 +92,15 @@
   return data_path.Append(indexed_db::GetSqliteDbDirectory(bucket_locator));
 }
 
-// Creates a task runner suitable for use either as the main IDB thread or for a
-// backing store. See https://crbug.com/329221141 for notes on task priority.
-scoped_refptr<base::SequencedTaskRunner> CreateTaskRunner() {
-  return base::ThreadPool::CreateSequencedTaskRunner(
-      {base::MayBlock(), base::WithBaseSyncPrimitives(),
-       base::FeatureList::IsEnabled(base::kUseUtilityThreadGroup)
-           ? base::TaskPriority::USER_BLOCKING
-           : base::TaskPriority::USER_VISIBLE,
-       // BLOCK_SHUTDOWN to support clearing session-only storage.
-       base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
+// Task traits suitable for use either as the main IDB thread or for a backing
+// store. See https://crbug.com/329221141 for notes on task priority.
+base::TaskTraits GetTaskTraits() {
+  return {base::MayBlock(), base::WithBaseSyncPrimitives(),
+          base::FeatureList::IsEnabled(base::kUseUtilityThreadGroup)
+              ? base::TaskPriority::USER_BLOCKING
+              : base::TaskPriority::USER_VISIBLE,
+          // BLOCK_SHUTDOWN to support clearing session-only storage.
+          base::TaskShutdownBehavior::BLOCK_SHUTDOWN};
 }
 
 bool IsAllowedPath(const std::vector<base::FilePath>& allowed_paths,
@@ -235,15 +234,23 @@
     mojo::PendingRemote<storage::mojom::FileSystemAccessContext>
         file_system_access_context,
     scoped_refptr<base::SequencedTaskRunner> custom_task_runner)
-    : idb_task_runner_(custom_task_runner ? custom_task_runner
-                                          : CreateTaskRunner()),
-      base_data_path_(base_data_path.empty() ? base::FilePath()
-                                             : base_data_path),
+    : idb_task_runner_(custom_task_runner),
+      base_data_path_(base_data_path),
       quota_manager_proxy_(std::move(quota_manager_proxy)),
       quota_client_receiver_(&quota_client_wrapper_),
       force_single_thread_(!!custom_task_runner) {
   TRACE_EVENT0("IndexedDB", "init");
 
+  if (!idb_task_runner_) {
+    if (in_memory()) {
+      idb_task_runner_ =
+          base::ThreadPool::CreateSequencedTaskRunner(GetTaskTraits());
+    } else {
+      idb_task_runner_ = base::ThreadPool::CreateSequencedTaskRunnerForResource(
+          GetTaskTraits(), base_data_path_);
+    }
+  }
+
   // QuotaManagerProxy::RegisterClient() must be called during construction
   // until crbug.com/1182630 is fixed.
   mojo::PendingRemote<storage::mojom::QuotaClient> quota_client_remote;
@@ -252,7 +259,7 @@
   quota_manager_proxy_->RegisterClient(
       std::move(quota_client_remote),
       storage::QuotaClientType::kIndexedDatabase);
-  IDBTaskRunner()->PostTask(
+  idb_task_runner()->PostTask(
       FROM_HERE, base::BindOnce(&IndexedDBContextImpl::BindPipesOnIDBSequence,
                                 weak_factory_.GetWeakPtr(),
                                 std::move(quota_client_receiver),
@@ -267,7 +274,7 @@
         pending_blob_storage_context,
     mojo::PendingRemote<storage::mojom::FileSystemAccessContext>
         pending_file_system_access_context) {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
   if (pending_quota_client_receiver) {
     quota_client_receiver_.Bind(std::move(pending_quota_client_receiver));
   }
@@ -282,7 +289,7 @@
 
 void IndexedDBContextImpl::BindControlOnIDBSequence(
     mojo::PendingReceiver<storage::mojom::IndexedDBControl> control) {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
   // We cannot run this in the constructor it needs to be async, but the async
   // tasks might not finish before the destructor runs.
   InitializeFromFilesIfNeeded(base::DoNothing());
@@ -291,7 +298,7 @@
 
 void IndexedDBContextImpl::BindControl(
     mojo::PendingReceiver<storage::mojom::IndexedDBControl> control) {
-  IDBTaskRunner()->PostTask(
+  idb_task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&IndexedDBContextImpl::BindControlOnIDBSequence,
                      weak_factory_.GetWeakPtr(), std::move(control)));
@@ -345,10 +352,10 @@
 
 void IndexedDBContextImpl::DeleteBucketData(const BucketLocator& bucket_locator,
                                             DeleteBucketDataCallback callback) {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
   DCHECK(!callback.is_null());
   ForceClose(
-      bucket_locator.id,
+      bucket_locator,
       storage::mojom::ForceCloseReason::FORCE_CLOSE_DELETE_ORIGIN,
       base::BindOnce(&IndexedDBContextImpl::DidForceCloseForDeleteBucketData,
                      weak_factory_.GetWeakPtr(), bucket_locator,
@@ -365,14 +372,9 @@
     return;
   }
 
-  if (!base::DirectoryExists(GetDataPath(bucket_locator))) {
-    std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk);
-    return;
-  }
-
-  bool success = std::ranges::all_of(GetStoragePaths(bucket_locator),
-                                     &base::DeletePathRecursively);
   NotifyOfBucketModification(bucket_locator);
+  bool success =
+      !std::ranges::any_of(GetStoragePaths(bucket_locator), &base::PathExists);
   if (success) {
     bucket_set_.erase(bucket_locator);
     bucket_size_map_.erase(bucket_locator);
@@ -384,15 +386,37 @@
 void IndexedDBContextImpl::ForceClose(storage::BucketId bucket_id,
                                       storage::mojom::ForceCloseReason reason,
                                       base::OnceClosure closure) {
+  auto bucket_locator = LookUpBucket(bucket_id);
+  if (bucket_locator) {
+    ForceClose(*bucket_locator, reason, std::move(closure));
+  } else if (closure) {
+    std::move(closure).Run();
+  }
+}
+
+void IndexedDBContextImpl::ForceClose(const storage::BucketLocator& bucket,
+                                      storage::mojom::ForceCloseReason reason,
+                                      base::OnceClosure closure) {
   const bool doom =
       reason == storage::mojom::ForceCloseReason::FORCE_CLOSE_DELETE_ORIGIN;
-  auto iter = bucket_contexts_.find(bucket_id);
+  auto iter = bucket_contexts_.find(bucket.id);
   if (iter != bucket_contexts_.end()) {
-    iter->second.AsyncCall(&BucketContext::ForceClose)
-        .WithArgs(doom, GetForceCloseReasonString(reason))
-        .Then(std::move(closure));
+    if (closure) {
+      iter->second.AsyncCall(&BucketContext::ForceClose)
+          .WithArgs(doom, GetForceCloseReasonString(reason))
+          .Then(std::move(closure));
+    } else {
+      iter->second.AsyncCall(&BucketContext::ForceClose)
+          .WithArgs(doom, GetForceCloseReasonString(reason));
+    }
   } else {
-    std::move(closure).Run();
+    if (doom) {
+      std::ranges::for_each(GetStoragePaths(bucket),
+                            &base::DeletePathRecursively);
+    }
+    if (closure) {
+      std::move(closure).Run();
+    }
   }
 }
 
@@ -434,10 +458,6 @@
     return;
   }
 
-  ForceClose(bucket_id,
-             storage::mojom::ForceCloseReason::FORCE_CLOSE_INTERNALS_PAGE,
-             base::DoNothing());
-
   base::ScopedTempDir temp_dir;
   if (!temp_dir.CreateUniqueTempDir()) {
     std::move(callback).Run(success, base::FilePath(), base::FilePath());
@@ -462,7 +482,7 @@
 
 void IndexedDBContextImpl::GetAllBucketsDetails(
     GetAllBucketsDetailsCallback callback) {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
   InitializeFromFilesIfNeeded(base::BindOnce(
       [](base::WeakPtr<IndexedDBContextImpl> handler,
          GetAllBucketsDetailsCallback callback) {
@@ -488,7 +508,7 @@
 void IndexedDBContextImpl::ContinueGetAllBucketsDetails(
     GetAllBucketsDetailsCallback callback,
     std::vector<storage::QuotaErrorOr<storage::BucketInfo>> bucket_infos) {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
 
   // This barrier receives the bucket info from individual bucket contexts and
   // invokes the next step in the process, `FinishGetAllBucketsDetails`.
@@ -526,13 +546,13 @@
 }
 
 void IndexedDBContextImpl::SetForceKeepSessionState() {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
   force_keep_session_state_ = true;
 }
 
 void IndexedDBContextImpl::ApplyPolicyUpdates(
     std::vector<storage::mojom::StoragePolicyUpdatePtr> policy_updates) {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
   for (const storage::mojom::StoragePolicyUpdatePtr& update : policy_updates) {
     if (!update->purge_on_shutdown) {
       origins_to_purge_on_shutdown_.erase(update->origin);
@@ -544,13 +564,13 @@
 
 void IndexedDBContextImpl::BindTestInterfaceForTesting(
     mojo::PendingReceiver<storage::mojom::IndexedDBControlTest> receiver) {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
   test_receivers_.Add(this, std::move(receiver));
 }
 
 void IndexedDBContextImpl::AddObserver(
     mojo::PendingRemote<storage::mojom::IndexedDBObserver> observer) {
-  IDBTaskRunner()->PostTask(
+  idb_task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(
           [](base::WeakPtr<IndexedDBContextImpl> context,
@@ -622,7 +642,7 @@
          BucketContext* _) {
         callback_task_runner->PostTask(FROM_HERE, std::move(callback));
       },
-      std::move(callback), IDBTaskRunner()));
+      std::move(callback), idb_task_runner()));
 }
 
 void IndexedDBContextImpl::GetUsageForTesting(
@@ -647,7 +667,7 @@
 
 std::optional<BucketLocator> IndexedDBContextImpl::LookUpBucket(
     storage::BucketId bucket_id) {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
   auto bucket_locator =
       std::ranges::find(bucket_set_, bucket_id, &BucketLocator::id);
   if (bucket_locator == bucket_set_.end()) {
@@ -683,7 +703,7 @@
 
 int64_t IndexedDBContextImpl::GetBucketDiskUsage(
     const BucketLocator& bucket_locator) {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
   DCHECK(!in_memory());
   if (!LookUpBucket(bucket_locator.id)) {
     return 0;
@@ -706,7 +726,7 @@
 
 base::Time IndexedDBContextImpl::GetBucketLastModified(
     const BucketLocator& bucket_locator) {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
   if (!LookUpBucket(bucket_locator.id)) {
     return base::Time();
   }
@@ -743,7 +763,7 @@
     return base::FilePath();
   }
 
-  if (indexed_db::ShouldUseLegacyFilePath(bucket_locator)) {
+  if (ShouldUseLegacyFilePath(bucket_locator)) {
     // First-party idb files for the default, for legacy reasons, are stored at:
     // {{storage_partition_path}}/IndexedDB/
     // TODO(crbug.com/40221733): Migrate all first party buckets to the new
@@ -789,7 +809,7 @@
 }
 
 IndexedDBContextImpl::~IndexedDBContextImpl() {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
 
   // Invalidate the weak pointers that bind `on_ready_for_destruction` (among
   // other callbacks) so that `ForceClose()` below doesn't mutate
@@ -802,28 +822,31 @@
   bucket_contexts_.clear();
   task_runner_limiters_.clear();
 
-  // Shutdown won't go through `ShutdownOnIDBSequence()` for in-memory DBs and
-  // in some tests.
-  if (!shutdown_start_time_.is_null()) {
+  if (!in_memory()) {
     base::UmaHistogramTimes("IndexedDB.ContextShutdownDuration",
                             base::TimeTicks::Now() - shutdown_start_time_);
   }
 }
 
-void IndexedDBContextImpl::ShutdownOnIDBSequence(base::TimeTicks start_time) {
-  // `this` will be destroyed when this method returns.
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+void IndexedDBContextImpl::ShutdownOnIDBSequence(
+    base::TimeTicks start_time,
+    base::OnceClosure purge_origins) {
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
 
   shutdown_start_time_ = start_time;
 
-  if (force_keep_session_state_) {
+  if (force_keep_session_state_ || origins_to_purge_on_shutdown_.empty() ||
+      in_memory()) {
+    // `this` is owned by `purge_origins`, so will be deleted now.
     return;
   }
 
-  // Clear session-only databases.
-  if (origins_to_purge_on_shutdown_.empty()) {
-    return;
-  }
+  InitializeFromFilesIfNeeded(std::move(purge_origins));
+}
+
+void IndexedDBContextImpl::PurgeOrigins() {
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
+  // `this` will be destroyed when this method returns.
 
   for (const BucketLocator& bucket_locator : bucket_set_) {
     // Delete the storage if its origin matches one of the origins to purge, or
@@ -841,13 +864,9 @@
     }
 
     if (delete_bucket) {
-      ForceClose(bucket_locator.id, {},
-                 base::BindOnce(
-                     [](std::vector<base::FilePath> paths) {
-                       std::ranges::for_each(paths,
-                                             &base::DeletePathRecursively);
-                     },
-                     GetStoragePaths(bucket_locator)));
+      ForceClose(bucket_locator,
+                 storage::mojom::ForceCloseReason::FORCE_CLOSE_DELETE_ORIGIN,
+                 {});
     }
   }
 }
@@ -855,22 +874,15 @@
 // static
 void IndexedDBContextImpl::Shutdown(
     std::unique_ptr<IndexedDBContextImpl> context) {
-  IndexedDBContextImpl* context_ptr = context.get();
-
   // Important: This function is NOT called on the IDB Task Runner. All variable
   // access must be thread-safe.
-  if (context->in_memory()) {
-    context_ptr->IDBTaskRunner()->DeleteSoon(FROM_HERE, std::move(context));
-    return;
-  }
-
-  context_ptr->IDBTaskRunner()->PostTask(
+  IndexedDBContextImpl* context_ptr = context.get();
+  context_ptr->idb_task_runner()->PostTask(
       FROM_HERE,
-      base::BindOnce(
-          &IndexedDBContextImpl::InitializeFromFilesIfNeeded,
-          base::Unretained(context_ptr),
-          base::BindOnce(&IndexedDBContextImpl::ShutdownOnIDBSequence,
-                         std::move(context), base::TimeTicks::Now())));
+      base::BindOnce(&IndexedDBContextImpl::ShutdownOnIDBSequence,
+                     base::Unretained(context_ptr), base::TimeTicks::Now(),
+                     base::BindOnce(&IndexedDBContextImpl::PurgeOrigins,
+                                    std::move(context))));
 }
 
 int64_t IndexedDBContextImpl::ReadUsageFromDisk(
@@ -921,7 +933,7 @@
 
 void IndexedDBContextImpl::InitializeFromFilesIfNeeded(
     base::OnceClosure callback) {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
   if (did_initialize_from_files_) {
     std::move(callback).Run();
     return;
@@ -996,7 +1008,7 @@
 
 std::map<StorageKey, base::FilePath>
 IndexedDBContextImpl::FindLegacyIndexedDBFiles() const {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
 
   base::FilePath data_path = GetLegacyDataPath();
   if (data_path.empty())
@@ -1031,7 +1043,7 @@
 
 std::vector<storage::BucketId>
 IndexedDBContextImpl::FindBucketsWithIndexedDBDirs() const {
-  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK(idb_task_runner()->RunsTasksInCurrentSequence());
 
   std::vector<storage::BucketId> bucket_ids;
   if (base_data_path_.empty())
@@ -1154,17 +1166,19 @@
   static int kTaskRunnerCountLimit = base::SysInfo::NumberOfProcessors();
   if (++task_runner_limiter.active_bucket_count > kTaskRunnerCountLimit) {
     if (!task_runner_limiter.overflow_task_runner) {
-      task_runner_limiter.overflow_task_runner = CreateTaskRunner();
+      task_runner_limiter.overflow_task_runner =
+          base::ThreadPool::CreateSequencedTaskRunner(GetTaskTraits());
     }
     bucket_task_runner = task_runner_limiter.overflow_task_runner;
   } else {
-    bucket_task_runner = CreateTaskRunner();
+    bucket_task_runner =
+        base::ThreadPool::CreateSequencedTaskRunner(GetTaskTraits());
   }
 
   const auto& [iter, inserted] = bucket_contexts_.emplace(
       bucket_locator.id,
       base::SequenceBound<BucketContext>(
-          force_single_thread_ ? IDBTaskRunner()
+          force_single_thread_ ? idb_task_runner()
                                : std::move(bucket_task_runner),
           bucket, data_directory, std::move(bucket_delegate),
           quota_manager_proxy_, std::move(cloned_blob_storage_context),
diff --git a/content/browser/indexed_db/indexed_db_context_impl.h b/content/browser/indexed_db/indexed_db_context_impl.h
index 0ee31ab..729c0b1 100644
--- a/content/browser/indexed_db/indexed_db_context_impl.h
+++ b/content/browser/indexed_db/indexed_db_context_impl.h
@@ -58,7 +58,7 @@
       public storage::mojom::QuotaClient {
  public:
   // If `base_data_path` is empty, nothing will be saved to disk.
-  // This is *not* called on the IDBTaskRunner, unlike most other functions.
+  // This is *not* called on the idb_task_runner, unlike most other functions.
   IndexedDBContextImpl(
       const base::FilePath& base_data_path,
       scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
@@ -70,7 +70,7 @@
 
   ~IndexedDBContextImpl() override;
 
-  // Called to initiate shutdown. This is *not* called on the IDBTaskRunner.
+  // Called to initiate shutdown. This is *not* called on the idb_task_runner.
   static void Shutdown(std::unique_ptr<IndexedDBContextImpl> context);
 
   IndexedDBContextImpl(const IndexedDBContextImpl&) = delete;
@@ -146,7 +146,7 @@
   bool BucketContextExists(storage::BucketId bucket_id);
 
   // Exposed for testing.
-  const scoped_refptr<base::SequencedTaskRunner>& IDBTaskRunner() const {
+  const scoped_refptr<base::SequencedTaskRunner>& idb_task_runner() const {
     return idb_task_runner_;
   }
 
@@ -178,13 +178,17 @@
           client_state_checker_remote,
       mojo::PendingReceiver<blink::mojom::IDBFactory> receiver,
       storage::QuotaErrorOr<storage::BucketInfo> bucket_info);
-  void ForceCloseImpl(
-      const storage::mojom::ForceCloseReason reason,
-      base::OnceClosure closure,
-      const std::optional<storage::BucketLocator>& bucket_locator);
 
-  // Always run immediately before destruction.
-  void ShutdownOnIDBSequence(base::TimeTicks start_time);
+  void ForceClose(const storage::BucketLocator& bucket_locator,
+                  storage::mojom::ForceCloseReason reason,
+                  base::OnceClosure callback);
+
+  // Always run immediately before destruction. `purge_origins` owns `this` and
+  // should be run only if it's necessary to delete data for some origins before
+  // destruction of `this`.
+  void ShutdownOnIDBSequence(base::TimeTicks start_time,
+                             base::OnceClosure purge_origins);
+  void PurgeOrigins();
 
   base::FilePath GetDataPath(
       const storage::BucketLocator& bucket_locator) const;
@@ -275,7 +279,7 @@
 
   bool in_memory() const { return base_data_path_.empty(); }
 
-  const scoped_refptr<base::SequencedTaskRunner> idb_task_runner_;
+  scoped_refptr<base::SequencedTaskRunner> idb_task_runner_;
 
   // Bound and accessed on the `idb_task_runner_`.
   mojo::Remote<storage::mojom::BlobStorageContext> blob_storage_context_;
diff --git a/content/browser/indexed_db/indexed_db_unittest.cc b/content/browser/indexed_db/indexed_db_unittest.cc
index d14d5de..f8c46750 100644
--- a/content/browser/indexed_db/indexed_db_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_unittest.cc
@@ -379,7 +379,7 @@
 
   void RunPostedTasks() {
     base::RunLoop loop;
-    context_->IDBTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure());
+    context_->idb_task_runner()->PostTask(FROM_HERE, loop.QuitClosure());
     loop.Run();
   }
 
@@ -591,8 +591,8 @@
 
   base::RunLoop loop;
   connection = std::make_unique<TestDatabaseConnection>(
-      context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName, kDBVersion,
-      kTransactionId);
+      context()->idb_task_runner(), ToOrigin(kOrigin), kDatabaseName,
+      kDBVersion, kTransactionId);
   EXPECT_CALL(
       *connection->open_callbacks,
       MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true),
@@ -634,8 +634,8 @@
   base::RunLoop loop;
   // Open connection.
   connection = std::make_unique<TestDatabaseConnection>(
-      context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName, kDBVersion,
-      kTransactionId);
+      context()->idb_task_runner(), ToOrigin(kOrigin), kDatabaseName,
+      kDBVersion, kTransactionId);
 
   EXPECT_CALL(
       *connection->open_callbacks,
@@ -708,8 +708,8 @@
   base::RunLoop loop;
   // Open connection 1, and expect the upgrade needed.
   connection1 = std::make_unique<TestDatabaseConnection>(
-      context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName, kDBVersion,
-      kTransactionId);
+      context()->idb_task_runner(), ToOrigin(kOrigin), kDatabaseName,
+      kDBVersion, kTransactionId);
 
   EXPECT_CALL(
       *connection1->open_callbacks,
@@ -734,8 +734,8 @@
       base::BarrierClosure(3, loop2.QuitClosure());
 
   connection2 = std::make_unique<TestDatabaseConnection>(
-      context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName, kDBVersion,
-      0);
+      context()->idb_task_runner(), ToOrigin(kOrigin), kDatabaseName,
+      kDBVersion, 0);
 
   // Check that we're called in order and the second connection gets it's
   // database after the first connection completes.
@@ -805,8 +805,8 @@
   base::RunLoop loop;
   // Open connection.
   connection = std::make_unique<TestDatabaseConnection>(
-      context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName, kDBVersion,
-      kTransactionId);
+      context()->idb_task_runner(), ToOrigin(kOrigin), kDatabaseName,
+      kDBVersion, kTransactionId);
 
   EXPECT_CALL(
       *connection->open_callbacks,
@@ -907,7 +907,7 @@
   {
     base::RunLoop loop;
     connection = std::make_unique<TestDatabaseConnection>(
-        context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName,
+        context()->idb_task_runner(), ToOrigin(kOrigin), kDatabaseName,
         kDBVersion, kTransactionId);
 
     EXPECT_CALL(
@@ -983,7 +983,7 @@
   {
     base::RunLoop loop;
     connection1 = std::make_unique<TestDatabaseConnection>(
-        context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName,
+        context()->idb_task_runner(), ToOrigin(kOrigin), kDatabaseName,
         kDBVersion1, kTransactionId1);
 
     EXPECT_CALL(
@@ -1052,7 +1052,7 @@
         base::BarrierClosure(2, loop.QuitClosure());
 
     connection2 = std::make_unique<TestDatabaseConnection>(
-        context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName,
+        context()->idb_task_runner(), ToOrigin(kOrigin), kDatabaseName,
         kDBVersion2, kTransactionId2);
 
     EXPECT_CALL(*connection2->open_callbacks,
@@ -1111,7 +1111,7 @@
     ::testing::InSequence dummy;
     base::RunLoop loop;
     connection3 = std::make_unique<TestDatabaseConnection>(
-        context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName,
+        context()->idb_task_runner(), ToOrigin(kOrigin), kDatabaseName,
         kDBVersion3, kTransactionId3);
 
     EXPECT_CALL(*connection3->open_callbacks,
@@ -1199,8 +1199,8 @@
   base::RunLoop loop;
   // Open connection 1.
   connection1 = std::make_unique<TestDatabaseConnection>(
-      context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName, kDBVersion1,
-      kTransactionId1);
+      context()->idb_task_runner(), ToOrigin(kOrigin), kDatabaseName,
+      kDBVersion1, kTransactionId1);
 
   EXPECT_CALL(
       *connection1->open_callbacks,
@@ -1272,8 +1272,8 @@
   // Open connection 2.
   base::RunLoop loop4;
   connection2 = std::make_unique<TestDatabaseConnection>(
-      context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName, kDBVersion2,
-      kTransactionId2);
+      context()->idb_task_runner(), ToOrigin(kOrigin), kDatabaseName,
+      kDBVersion2, kTransactionId2);
 
   EXPECT_CALL(
       *connection2->open_callbacks,
@@ -1346,8 +1346,8 @@
   base::RunLoop loop;
   // Open connection.
   connection = std::make_unique<TestDatabaseConnection>(
-      context()->IDBTaskRunner(), ToOrigin(kOrigin), kDatabaseName, kDBVersion,
-      kTransactionId);
+      context()->idb_task_runner(), ToOrigin(kOrigin), kDatabaseName,
+      kDBVersion, kTransactionId);
 
   EXPECT_CALL(
       *connection->open_callbacks,
@@ -1649,16 +1649,12 @@
 
   // Open the database again, without waiting for any of the previous steps to
   // finish. The timing of this is very particular, which is why this test does
-  // not use `VerifyForcedClosedCalled()`. If the second open() comes any later,
-  // it will succeed because the original Database will have finished being
-  // deleted. We want to verify that there is no crash in the situation where
-  // the second open is handled while the database is still in the process of
-  // being deleted.
+  // not use `VerifyForcedClosedCalled()`. The second open succeeds because the
+  // `DeleteDatabase` call synchronously destroyed the DB.
   MockMojoFactoryClient client2;
-  EXPECT_CALL(client2, Error);
   MockMojoDatabaseCallbacks database_callbacks2;
   base::RunLoop run_loop_for_second_open;
-  EXPECT_CALL(database_callbacks2, ForcedClose())
+  EXPECT_CALL(client2, MockedUpgradeNeeded)
       .WillOnce(
           ::base::test::RunClosure(run_loop_for_second_open.QuitClosure()));
   mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote2;
diff --git a/content/browser/indexed_db/instance/bucket_context.cc b/content/browser/indexed_db/instance/bucket_context.cc
index 74ee5aa..22c127bf 100644
--- a/content/browser/indexed_db/instance/bucket_context.cc
+++ b/content/browser/indexed_db/instance/bucket_context.cc
@@ -235,12 +235,13 @@
   {
     // This handle keeps `this` from closing until it goes out of scope.
     BucketContextHandle handle(*this);
-    for (const auto& [name, database] : databases_) {
-      // Note: We purposefully ignore the result here as force close needs to
-      // continue tearing things down anyways.
-      database->ForceCloseAndRunTasks(SanitizeErrorMessage(message));
+    for (auto iter = databases_.begin(); iter != databases_.end();
+         iter = databases_.erase(iter)) {
+      // The result is irrelevant as the database and backing store are already
+      // closing.
+      std::move(*iter->second).ForceClose(SanitizeErrorMessage(message));
     }
-    databases_.clear();
+    CHECK(databases_.empty());
     has_blobs_outstanding_ = false;
     close_timer_.Stop();
     if (backing_store()) {
@@ -613,21 +614,6 @@
     database_ptr = CreateAndAddDatabase(name);
   } else {
     database_ptr = it->second.get();
-
-    // The `Database` might have been forced closed by dev tools, in which case
-    // no new connections should be added. The `Database` should be deleted
-    // *soon* in this case, but the request can arrive while `RunTasks()` is
-    // still queued. We could try to reschedule this open() request, but if the
-    // open request had already made it to ConnectionCoordinator, it would be
-    // pruned and errors reported: see `ShouldPruneForForceClose()`. So do that
-    // here too.
-    if (!database_ptr->IsAcceptingConnections()) {
-      std::move(connection->factory_client)
-          ->Error(blink::mojom::IDBException::kAbortError,
-                  u"The connection was closed.");
-      connection->database_callbacks->OnForcedClose();
-      return;
-    }
   }
 
   database_ptr->ScheduleOpenConnection(std::move(connection));
@@ -650,18 +636,26 @@
   // First, check the databases that are already represented by
   // `Database` objects. If one exists, schedule it to be deleted and
   // we're done.
-  auto it = databases_.find(name);
-  if (it != databases_.end()) {
+  auto delete_database = [&]() {
+    auto it = databases_.find(name);
+    if (it == databases_.end()) {
+      return false;
+    }
     CHECK(backing_store_);
-    base::WeakPtr<Database> database = it->second->AsWeakPtr();
-    database->ScheduleDeleteDatabase(std::move(factory_client),
-                                     std::move(on_deletion_complete));
+    it->second->ScheduleDeleteDatabase(std::move(factory_client),
+                                       std::move(on_deletion_complete));
     if (force_close) {
-      Status status = database->ForceCloseAndRunTasks(force_close_message);
-      if (!status.ok()) {
-        OnDatabaseError(database.get(), status, "Error aborting transactions.");
+      std::unique_ptr<Database> database = std::move(it->second);
+      databases_.erase(it);
+      Status status = std::move(*database).ForceClose(force_close_message);
+      if (!status.ok() && !ShouldUseSqlite()) {
+        OnDatabaseError(nullptr, status, "Error aborting transactions.");
       }
     }
+
+    return true;
+  };
+  if (delete_database()) {
     return;
   }
 
@@ -706,17 +700,10 @@
     return;
   }
 
-  // If it exists but does not already have an `Database` object,
+  // If it exists but does not already have a `Database` object,
   // create it and initiate deletion.
-  Database* database_ptr = CreateAndAddDatabase(name);
-  database_ptr->ScheduleDeleteDatabase(std::move(factory_client),
-                                       std::move(on_deletion_complete));
-  if (force_close) {
-    Status status = database_ptr->ForceCloseAndRunTasks(force_close_message);
-    if (!status.ok()) {
-      OnDatabaseError(database_ptr, status, "Error aborting transactions.");
-    }
-  }
+  CreateAndAddDatabase(name);
+  CHECK(delete_database());
 }
 
 storage::mojom::IdbBucketMetadataPtr BucketContext::FillInMetadata(
@@ -759,7 +746,7 @@
 
 Database* BucketContext::CreateAndAddDatabase(const std::u16string& name) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(!base::Contains(databases_, name));
+  CHECK(!base::Contains(databases_, name));
   auto database =
       std::make_unique<Database>(next_database_id_for_locks_++, name, *this);
   return databases_.emplace(name, std::move(database)).first->second.get();
@@ -932,9 +919,17 @@
       message.empty() ? status.ToString() : message;
   if (ShouldUseSqlite()) {
     // Unlike in the LevelDB case, an error in one database doesn't indicate a
-    // problem with the entire bucket.
+    // problem with the entire bucket, so we just `ForceClose` the one
+    // `Database`.
     CHECK(database);
-    database->ForceCloseAndRunTasks(error_message);
+    // Error during force close; `database` was already removed.
+    if (database->force_closing()) {
+      return;
+    }
+    auto iter = databases_.find(database->name());
+    CHECK(iter != databases_.end());
+    std::move(*iter->second).ForceClose(error_message);
+    databases_.erase(iter);
   } else {
     if (status.IsCorruption()) {
       HandleBackingStoreCorruption(error_message);
@@ -1137,6 +1132,22 @@
                             base::TimeTicks::Now() - start);
   }
 
+  if (is_doomed_) {
+    if (ShouldUseLegacyFilePath(bucket_locator())) {
+      if (ShouldUseSqlite()) {
+        base::DeletePathRecursively(
+            data_path_.Append(GetSqliteDbDirectory(bucket_locator())));
+      } else {
+        base::DeletePathRecursively(
+            data_path_.Append(GetLevelDBFileName(bucket_locator())));
+        base::DeletePathRecursively(
+            data_path_.Append(GetBlobStoreFileName(bucket_locator())));
+      }
+    } else {
+      base::DeletePathRecursively(data_path_);
+    }
+  }
+
   task_run_queued_ = false;
   is_doomed_ = false;
   bucket_space_check_callbacks_ = {};
diff --git a/content/browser/indexed_db/instance/bucket_context.h b/content/browser/indexed_db/instance/bucket_context.h
index 60b9ea9..947222e6 100644
--- a/content/browser/indexed_db/instance/bucket_context.h
+++ b/content/browser/indexed_db/instance/bucket_context.h
@@ -156,8 +156,10 @@
   // Returns true if a RunTask invocation is queued. To be used by metrics.
   bool task_run_queued() const { return task_run_queued_; }
 
-  // Normally, in-memory bucket contexts never self-close. If this is called
-  // with `doom` set to true, they will self-close.
+  // Closes the bucket context, i.e. closes the backing store and closes Mojo
+  // connections to renderers. When `doom` is true, the directories containing
+  // data will also be deleted. Normally, in-memory bucket contexts never close.
+  // If this is called with `doom` set to true, they will close.
   void ForceClose(bool doom, const std::string& message);
 
   // Starts capturing state data for indexeddb-internals. The data will be
diff --git a/content/browser/indexed_db/instance/database.cc b/content/browser/indexed_db/instance/database.cc
index 5cb680af..3e76e1c 100644
--- a/content/browser/indexed_db/instance/database.cc
+++ b/content/browser/indexed_db/instance/database.cc
@@ -386,15 +386,8 @@
   return num_transactions;
 }
 
-Status Database::ForceCloseAndRunTasks(const std::string& message) {
-  if (!bucket_context_->ShouldUseSqlite()) {
-    CHECK(!force_closing_);
-  } else if (force_closing_) {
-    // Re-entrancy can validly occur if there's an error in the code below,
-    // e.g. in `CloseAndReportForceClose`.
-    return Status::OK();
-  }
-
+Status Database::ForceClose(const std::string& message) && {
+  CHECK(!force_closing_);
   force_closing_ = true;
   for (Connection* connection : connections_) {
     connection->CloseAndReportForceClose(message);
@@ -415,14 +408,12 @@
   } while (task_state != ConnectionCoordinator::ExecuteTaskResult::kDone &&
            task_state != ConnectionCoordinator::ExecuteTaskResult::kError);
   CHECK(connections_.empty());
-  bucket_context_->QueueRunTasks();
   return status;
 }
 
 void Database::ScheduleOpenConnection(
     std::unique_ptr<PendingConnection> connection) {
-  CHECK(IsAcceptingConnections());
-
+  CHECK(!force_closing_);
   connection_coordinator_.ScheduleOpenConnection(std::move(connection));
 }
 
diff --git a/content/browser/indexed_db/instance/database.h b/content/browser/indexed_db/instance/database.h
index e34e857..245a833c 100644
--- a/content/browser/indexed_db/instance/database.h
+++ b/content/browser/indexed_db/instance/database.h
@@ -96,10 +96,12 @@
   Status RunTasks();
   void RegisterAndScheduleTransaction(Transaction* transaction);
 
-  // The database object (this object) must be kept alive for the duration of
-  // this call. This means the caller should own an
-  // BucketContextHandle while calling this methods.
-  Status ForceCloseAndRunTasks(const std::string& message);
+  // This closes connections and their transactions, and tells the connection
+  // coordinator to cancel pending open requests. However, pending delete
+  // requests are honored (synchronously). This requires an rvalue reference
+  // because it should only be called right before destruction, by its owner
+  // (BucketContext).
+  Status ForceClose(const std::string& message) &&;
 
   void ScheduleOpenConnection(std::unique_ptr<PendingConnection> connection);
 
@@ -110,7 +112,7 @@
   // Number of connections that have progressed passed initial open call.
   size_t ConnectionCount() const { return connections_.size(); }
 
-  bool IsAcceptingConnections() const { return !force_closing_; }
+  bool force_closing() const { return force_closing_; }
 
   // Number of active open/delete calls (running or blocked on other
   // connections).
diff --git a/content/browser/indexed_db/instance/database_unittest.cc b/content/browser/indexed_db/instance/database_unittest.cc
index b655c1fb..e79c165 100644
--- a/content/browser/indexed_db/instance/database_unittest.cc
+++ b/content/browser/indexed_db/instance/database_unittest.cc
@@ -188,13 +188,13 @@
       upgrade_transaction_id, IndexedDBDatabaseMetadata::NO_VERSION,
       mojo::NullAssociatedReceiver());
   db_->ScheduleOpenConnection(std::move(connection));
+  db_ = nullptr;
 
   base::RunLoop run_loop;
   EXPECT_CALL(request, Error);
   EXPECT_CALL(database_callbacks, ForcedClose)
       .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
-  db_->ForceCloseAndRunTasks(kTestForceCloseMessage);
-  db_ = nullptr;
+  bucket_context_->ForceClose(false, kTestForceCloseMessage);
   run_loop.Run();
 }
 
@@ -261,10 +261,10 @@
   EXPECT_EQ(db_->ConnectionCount(), 1UL);
   EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
   EXPECT_EQ(db_->PendingOpenDeleteCount(), 1UL);
+  db_ = nullptr;
 
   EXPECT_FALSE(run_loop.AnyQuitCalled());
-  db_->ForceCloseAndRunTasks(kTestForceCloseMessage);
-  db_ = nullptr;
+  bucket_context_->ForceClose(false, kTestForceCloseMessage);
   run_loop.Run();
   delete_success_loop.Run();
 
diff --git a/content/browser/indexed_db/instance/transaction_unittest.cc b/content/browser/indexed_db/instance/transaction_unittest.cc
index 6e842f18..c2c9a02 100644
--- a/content/browser/indexed_db/instance/transaction_unittest.cc
+++ b/content/browser/indexed_db/instance/transaction_unittest.cc
@@ -74,7 +74,10 @@
         /*is_incognito=*/false, temp_dir_.GetPath(),
         base::SingleThreadTaskRunner::GetCurrentDefault(),
         /*special_storage_policy=*/nullptr);
+    SetUpBucketContext();
+  }
 
+  void SetUpBucketContext() {
     BucketContext::Delegate delegate;
     delegate.on_ready_for_destruction = base::BindOnce(
         &TransactionTestBase::OnDbReadyForDestruction, base::Unretained(this));
@@ -396,7 +399,10 @@
     EXPECT_EQ(test_case.can_timeout ? 1 : 0, transaction->timeout_strikes_);
 
     // Clean up for the next iteration.
-    db_->ForceCloseAndRunTasks("The database is force-closed for testing.");
+    db_ = nullptr;
+    bucket_context_->ForceClose(false,
+                                "The database is force-closed for testing.");
+    SetUpBucketContext();
   }
 }
 
diff --git a/content/browser/media/capture/frame_test_util.cc b/content/browser/media/capture/frame_test_util.cc
index e46c85b..93250968 100644
--- a/content/browser/media/capture/frame_test_util.cc
+++ b/content/browser/media/capture/frame_test_util.cc
@@ -37,13 +37,8 @@
 void LoadStimsFromYUV(const uint8_t y_src[],
                       const uint8_t u_src[],
                       const uint8_t v_src[],
-                      int spanification_suspected_redundant_width,
                       base::span<TriStim> stims) {
-  // TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
-  // redundant in M143.
-  CHECK(spanification_suspected_redundant_width == stims.size(),
-        base::NotFatalUntil::M143);
-  for (int i = 0; i < spanification_suspected_redundant_width; ++i) {
+  for (size_t i = 0; i < stims.size(); ++i) {
     stims[i].SetPoint(UNSAFE_TODO(y_src[i]) / 255.0f,
                       UNSAFE_TODO(u_src[i / 2]) / 255.0f,
                       UNSAFE_TODO(v_src[i / 2]) / 255.0f);
@@ -52,12 +47,7 @@
 
 void LoadStimsFromYUV(const uint8_t y_src[],
                       const uint16_t uv_src[],
-                      int spanification_suspected_redundant_width,
                       base::span<TriStim> stims) {
-  // TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
-  // redundant in M143.
-  CHECK(spanification_suspected_redundant_width == stims.size(),
-        base::NotFatalUntil::M143);
 // https://docs.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering#nv12
 // "All of the Y samples appear first in memory as an array of unsigned char
 // values with an even number of lines. The Y plane is followed immediately by
@@ -66,14 +56,14 @@
 // little-endian WORD values, the LSBs contain the U values, and the MSBs
 // contain the V values."
 #if defined(SK_CPU_BENDIAN)
-  for (int i = 0; i < spanification_suspected_redundant_width; ++i) {
+  for (size_t i = 0; i < stims.size(); ++i) {
     stims[i].SetPoint(
         y_src[i] / 255.0f,
         (uv_src[i / 2] >> 8) / 255.0f,  // MSB contains U values on LE
         (uv_src[i / 2] & 0xFF) / 255.0f);
   }
 #else
-  for (int i = 0; i < spanification_suspected_redundant_width; ++i) {
+  for (size_t i = 0; i < stims.size(); ++i) {
     stims[i].SetPoint(UNSAFE_TODO(y_src[i]) / 255.0f,
                       (UNSAFE_TODO(uv_src[i / 2]) & 0xFF) /
                           255.0f,  // LSB contains U values on LE
@@ -91,13 +81,8 @@
 // Copies the array of TriStims to the BGRA/RGBA output, mapping
 // [0.0,1.0]⇒[0,255].
 void StimsToN32Row(base::span<const TriStim> row,
-                   int spanification_suspected_redundant_width,
                    base::span<uint8_t> bgra_out) {
-  // TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
-  // redundant in M143.
-  CHECK(spanification_suspected_redundant_width == row.size(),
-        base::NotFatalUntil::M143);
-  for (int i = 0; i < spanification_suspected_redundant_width; ++i) {
+  for (size_t i = 0; i < row.size(); ++i) {
     bgra_out[(i * 4) + (SK_R32_SHIFT / 8)] = QuantizeAndClamp(row[i].x());
     bgra_out[(i * 4) + (SK_G32_SHIFT / 8)] = QuantizeAndClamp(row[i].y());
     bgra_out[(i * 4) + (SK_B32_SHIFT / 8)] = QuantizeAndClamp(row[i].z());
@@ -140,7 +125,7 @@
                       (row / 2) * frame.stride(media::VideoFrame::Plane::kU)),
           UNSAFE_TODO(frame.visible_data(media::VideoFrame::Plane::kV) +
                       (row / 2) * frame.stride(media::VideoFrame::Plane::kV)),
-          bitmap.width(), stims);
+          stims);
     } else {
       CHECK_EQ(frame.format(), media::VideoPixelFormat::PIXEL_FORMAT_NV12);
       LoadStimsFromYUV(
@@ -149,12 +134,11 @@
           reinterpret_cast<const uint16_t*>(UNSAFE_TODO(
               frame.visible_data(media::VideoFrame::Plane::kUV) +
               (row / 2) * frame.stride(media::VideoFrame::Plane::kUV))),
-          bitmap.width(), stims);
+          stims);
     }
     transform->Transform(stims.data(), stims.size());
-    StimsToN32Row(
-        stims, bitmap.width(),
-        base::as_writable_byte_span(UNSAFE_SKBITMAP_GETADDR32(bitmap, 0, row)));
+    StimsToN32Row(stims, base::as_writable_byte_span(
+                             UNSAFE_SKBITMAP_GETADDR32(bitmap, 0, row)));
   }
 
   return bitmap;
diff --git a/content/browser/navigation_transitions/back_forward_transition_animator.cc b/content/browser/navigation_transitions/back_forward_transition_animator.cc
index 7c15b50..31b5bb2 100644
--- a/content/browser/navigation_transitions/back_forward_transition_animator.cc
+++ b/content/browser/navigation_transitions/back_forward_transition_animator.cc
@@ -58,8 +58,6 @@
 using AnimationAbortReason =
     BackForwardTransitionAnimator::AnimationAbortReason;
 
-static constexpr char kAnimationAbortedReason[] =
-    "Navigation.GestureTransition.AnimationAbortReason";
 static constexpr char kNewCommitInPrimaryMainFrame[] =
     "Navigation.GestureTransition.NewCommitInPrimaryMainFrame";
 static constexpr char kNewCommitWhileDisplayingCanceledAnimation[] =
@@ -418,11 +416,6 @@
 
   CHECK(IsTerminalState()) << StateToString(state_);
 
-  if (state_ == State::kAnimationFinished) {
-    base::UmaHistogramEnumeration(kAnimationAbortedReason,
-                                  AnimationAbortReason::kAnimationFinished);
-  }
-
   switch (ignoring_input_reason_) {
     case IgnoringInputReason::kAnimationInvokedOccurred: {
       base::UmaHistogramCounts100(
@@ -1258,10 +1251,6 @@
 
 void BackForwardTransitionAnimator::AbortAnimation(
     AnimationAbortReason abort_reason) {
-  TRACE_EVENT("browser,navigation",
-              "BackForwardTransitionAnimator::AbortAnimation", "abort_reason",
-              AnimationAbortReasonToString(abort_reason));
-  base::UmaHistogramEnumeration(kAnimationAbortedReason, abort_reason);
   abort_reason_ = abort_reason;
   AdvanceAndProcessState(State::kAnimationAborted);
 }
diff --git a/content/browser/navigation_transitions/back_forward_transition_animator.h b/content/browser/navigation_transitions/back_forward_transition_animator.h
index 9c07b25..739c4a1 100644
--- a/content/browser/navigation_transitions/back_forward_transition_animator.h
+++ b/content/browser/navigation_transitions/back_forward_transition_animator.h
@@ -131,8 +131,7 @@
 
   // Indicates the animation abort reason for UMA metrics.
   // These values are persisted to logs. Entries should not be renumbered and
-  // numeric values should never be reused. Upon adding a new value, add it
-  // to `tools/metrics/histograms/metadata/navigation/enums.xml` as well.
+  // numeric values should never be reused.
   enum class AnimationAbortReason {
     // The subscribed `RenderWidgetHost` was destroyed.
     kRenderWidgetHostDestroyed = 0,
diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc
index c710557..2b1aae5 100644
--- a/content/browser/renderer_host/media/media_stream_manager.cc
+++ b/content/browser/renderer_host/media/media_stream_manager.cc
@@ -641,6 +641,9 @@
   void SetVideoType(MediaStreamType video_type) {
     DCHECK(blink::IsVideoInputMediaType(video_type) ||
            video_type == MediaStreamType::NO_SERVICE);
+    SendLogMessage(base::StringPrintf(
+        "DR::SetVideoType([requester_id=%d] {video_type=%s})", requester_id,
+        StreamTypeToString(video_type)));
     video_type_ = video_type;
   }
 
diff --git a/content/browser/webtransport/web_transport_connector_impl.cc b/content/browser/webtransport/web_transport_connector_impl.cc
index 4e8cbf8..086e66a 100644
--- a/content/browser/webtransport/web_transport_connector_impl.cc
+++ b/content/browser/webtransport/web_transport_connector_impl.cc
@@ -70,14 +70,42 @@
       : frame_(std::move(frame)),
         url_(url),
         remote_(std::move(remote)),
-        tracker_(std::move(tracker)) {}
+        tracker_(std::move(tracker)) {
+    if (!tracker_) {
+      return;
+    }
+
+    std::string_view ip_string;
+    if (url.HostIsIPAddress()) {
+      ip_string = url.HostNoBracketsPiece();
+    } else if (net::IsLocalhost(url)) {
+      ip_string = "127.0.0.1";
+    } else {
+      return;
+    }
+
+    // Some decentralized apps may need to cancel requests to unresponsive
+    // hosts, so this penalty could cause too much impact on those use cases.
+    // Usually well-behaving apps might refer to hosts by plain IPs thather than
+    // DNS names, that's why the
+    // WebTransportConnectorImpl::InterceptingHandshakeClient tries to figure
+    // out the host's IP by checking GURL::HostIsIPAddress() and assign a valid
+    // server address before invoking the network process. If the connection is
+    // closed before the DNS request is completed, we may want to avoid
+    // penalties if the host address is already an plain IP.
+    auto ip_address = net::IPAddress();
+    if (ip_address.AssignFromIPLiteral(ip_string)) {
+      CHECK(ip_address.IsValid());
+      tracker_->SetServerAddress(ip_address);
+    }
+  }
 
   ~InterceptingHandshakeClient() override = default;
 
   // WebTransportHandshakeClient implementation:
   void OnBeforeConnect(const net::IPEndPoint& server_address) override {
     if (tracker_) {
-      tracker_->OnBeforeConnect(server_address);
+      tracker_->SetServerAddress(server_address.address());
     }
 
     // Here we pass an invalid IPEndPoint instance because it is dangerous to
@@ -192,6 +220,11 @@
     mojo::PendingRemote<network::mojom::WebTransportHandshakeClient>
         handshake_client,
     std::unique_ptr<WebTransportThrottleContext::Tracker> tracker) {
+  DVLOG(1) << "WebTransportConnectorImpl::OnThrottleDone -- "
+           << "tracker: " << tracker << " URL: " << url;
+  if (tracker) {
+    tracker->set_throttle_done();
+  }
   RenderProcessHost* process = RenderProcessHost::FromID(process_id_);
   if (!process) {
     return;
diff --git a/content/browser/webtransport/web_transport_throttle_context.cc b/content/browser/webtransport/web_transport_throttle_context.cc
index 4ebe86fd..1908418c 100644
--- a/content/browser/webtransport/web_transport_throttle_context.cc
+++ b/content/browser/webtransport/web_transport_throttle_context.cc
@@ -29,11 +29,9 @@
          !command_line->HasSwitch(switches::kWebTransportDeveloperMode);
 }
 
-std::optional<net::IPAddress> GetSubnetAddress(
-    const net::IPEndPoint& endpoint) {
+std::optional<net::IPAddress> GetSubnetAddress(const net::IPAddress& address) {
   // We don't have a way to get the actual subnet mask, so assuming /24 and /64
   // for IPv4 and IPv6 respectively.
-  const auto& address = endpoint.address();
   if (!address.IsValid()) {
     return std::nullopt;
   }
@@ -88,7 +86,7 @@
 
 base::TimeDelta
 WebTransportThrottleContext::PenaltyManager::ComputeHandshakePenalty(
-    const std::optional<net::IPEndPoint>& server_address) {
+    const std::optional<net::IPAddress>& server_address) {
   DVLOG(1) << "WebTransportThrottleContext::ComputeHandshakePenalty() this="
            << this;
 
@@ -100,14 +98,11 @@
   }
 
   if (!server_address) {
-    // TODO(https://crbug.com/40069954): Some decentralized apps may need to
-    // cancel requests to unresponsive hosts, so this penalty could cause too
-    // much impact on those use cases. Usually well-behaving apps might refer to
-    // hosts by plain IPs thather than DNS names, hence we can reduce the impact
-    // by checking GURL::HostIsIPAddress().
+    // This only happens if the Web Transport Server use a domain name and the
+    // connection is cancelled before the DNS request is completed.
     if (FailedHandshakeNeedsPenalty(net::IPAddress())) {
-      DVLOG(1)
-          << "Return max penalty when several requests are cancelled abruptly.";
+      DVLOG(1) << "Return max penalty when several requests to unknown server "
+                  "address are cancelled abruptly.";
       return base::Minutes(5);
     }
     DVLOG(1) << "Return min penalty for a requested cancelled before the "
@@ -115,27 +110,24 @@
     return base::Milliseconds(50);
   }
 
-  DVLOG(1) << " server_address=" << server_address->address().ToString();
-
-  if (FailedHandshakeNeedsPenalty(server_address->address())) {
-    DVLOG(1) << "Return max penalty for a request targetting the same address "
-                "and failed several times.";
+  if (FailedHandshakeNeedsPenalty(*server_address)) {
+    DVLOG(1) << "Return max penalty for a request targeting the "
+             << server_address->ToString() << " host and failed several times.";
     return base::Minutes(5);
   }
 
   auto net_address = GetSubnetAddress(*server_address);
   if (net_address) {
-    DVLOG(1) << " subnet_address=" << net_address->ToString();
-
     if (FailedHandshakeNeedsPenalty(*net_address)) {
-      DVLOG(1) << "Return mid penalty for a request targetting the same subnet "
-                  "and failed several times.";
+      DVLOG(1) << "Return mid penalty for a request targeting the "
+               << net_address->ToString()
+               << " subnet and failed several times.";
       return base::Minutes(2);
     }
   }
 
-  DVLOG(1)
-      << "Return default penalty for a request that failed for the first time.";
+  DVLOG(1) << "Return default penalty for a request that target "
+           << server_address->ToString() << " and failed for the first time.";
   return base::Milliseconds(100);
 }
 
@@ -207,17 +199,41 @@
 }
 
 WebTransportThrottleContext::Tracker::~Tracker() {
-  if (throttle_context_) {
-    throttle_context_->MaybeQueueHandshakeFailurePenalty(std::nullopt);
+  if (!throttle_context_) {
+    // The penalty has been already computed based on the handshake result.
+    return;
   }
+
+  if (!throttle_done_) {
+    // The request was cancelled during the throttling stage, before any network
+    // activity was performed. This can provide no benefit to an attacker, so
+    // there is no need to penalize it.
+    throttle_context_->RemovePendingHandshakes();
+    return;
+  }
+
+  // Handle early-cancellation scenarios, where the connection is cancelled
+  // before getting the handshake result.
+
+  if (server_address_.IsValid()) {
+    // Connection cancelled by the network process after throttling targeting a
+    // valid ip address, either because the DNS request has been resolved or
+    // because the URL's host was alreadu a valid IP address.
+    throttle_context_->MaybeQueueHandshakeFailurePenalty(server_address_);
+    return;
+  }
+
+  // Connection cancelled after throttling but before the DNS request has been
+  // resolved.
+  throttle_context_->MaybeQueueHandshakeFailurePenalty(std::nullopt);
 }
 
-void WebTransportThrottleContext::Tracker::OnBeforeConnect(
-    const net::IPEndPoint& server_address) {
+void WebTransportThrottleContext::Tracker::SetServerAddress(
+    const net::IPAddress& server_address) {
   DVLOG(1) << "WebTransportThrottleContext::Tracker::OnBeforeConnect()"
-           << " this=" << this;
+           << " server_address= " << server_address.ToString();
 
-  if (server_address.address().IsValid()) {
+  if (server_address.IsValid()) {
     server_address_ = server_address;
   }
 }
@@ -259,7 +275,8 @@
 WebTransportThrottleContext::PerformThrottle(
     ThrottleDoneCallback on_throttle_done) {
   DVLOG(1) << "WebTransportThrottleContext::PerformThrottle() this=" << this
-           << " pending_handshakes_=" << penalty_mgr_.PendingHandshakes();
+           << " pending_handshakes_=" << penalty_mgr_.PendingHandshakes()
+           << " throttled_connections_=" << throttled_connections_.size();
 
   if (!penalty_mgr_.PendingQueueTimerIsRunning()) {
     // If the timer was not running there may be some pending connections that
@@ -271,6 +288,8 @@
   if (penalty_mgr_.PendingHandshakes() +
           static_cast<int>(throttled_connections_.size()) >=
       kMaxPendingSessions) {
+    DVLOG(1) << "WebTransportThrottleContext::PerformThrottle() -- Too many "
+                "connections !!!";
     return ThrottleResult::kTooManyPendingSessions;
   }
 
@@ -289,7 +308,7 @@
 }
 
 void WebTransportThrottleContext::MaybeQueueHandshakeFailurePenalty(
-    const std::optional<net::IPEndPoint>& server_address) {
+    const std::optional<net::IPAddress>& server_address) {
   if (should_queue_handshake_failure_penalty_) {
     auto penalty = base::Minutes(5);
     if (IsFineGrainedThrottlingEnabled()) {
@@ -313,6 +332,11 @@
   }
 }
 
+void WebTransportThrottleContext::RemovePendingHandshakes() {
+  CHECK_GT(penalty_mgr_.PendingHandshakes(), 0);
+  penalty_mgr_.RemovePendingHandshakes();
+}
+
 void WebTransportThrottleContext::ScheduleThrottledConnection() {
   DVLOG(1) << "WebTransportThrottleContext::ScheduleThrottledConnection() this="
            << this
diff --git a/content/browser/webtransport/web_transport_throttle_context.h b/content/browser/webtransport/web_transport_throttle_context.h
index 117bbcece..ae5f62b9 100644
--- a/content/browser/webtransport/web_transport_throttle_context.h
+++ b/content/browser/webtransport/web_transport_throttle_context.h
@@ -46,7 +46,7 @@
 
     // Collects information about a WebTransport handshake that is about to
     // start.
-    void OnBeforeConnect(const net::IPEndPoint& server_address);
+    void SetServerAddress(const net::IPAddress& server_address);
 
     // Records the successful end of a WebTransport handshake.
     void OnHandshakeEstablished();
@@ -54,9 +54,12 @@
     // Records a WebTransport handshake failure.
     void OnHandshakeFailed();
 
+    void set_throttle_done() { throttle_done_ = true; }
+
    private:
     base::WeakPtr<WebTransportThrottleContext> throttle_context_;
-    net::IPEndPoint server_address_;
+    net::IPAddress server_address_;
+    bool throttle_done_ = false;
   };
 
   using ThrottleDoneCallback =
@@ -85,10 +88,12 @@
   // Called when a handshake fails. Adds handshake delays unless it is
   // explicitly suppressed.
   void MaybeQueueHandshakeFailurePenalty(
-      const std::optional<net::IPEndPoint>& server_address);
+      const std::optional<net::IPAddress>& server_address);
 
   void OnPendingQueueReady();
 
+  void RemovePendingHandshakes();
+
   base::WeakPtr<WebTransportThrottleContext> GetWeakPtr();
 
  private:
@@ -102,7 +107,7 @@
     ~PenaltyManager();
 
     base::TimeDelta ComputeHandshakePenalty(
-        const std::optional<net::IPEndPoint>& server_address);
+        const std::optional<net::IPAddress>& server_address);
 
     // Queues a pending handshake to be considered complete after `after`.
     void QueuePending(base::TimeDelta after);
diff --git a/content/browser/webtransport/web_transport_throttle_context_unittest.cc b/content/browser/webtransport/web_transport_throttle_context_unittest.cc
index 6c1adae0..875aa0b 100644
--- a/content/browser/webtransport/web_transport_throttle_context_unittest.cc
+++ b/content/browser/webtransport/web_transport_throttle_context_unittest.cc
@@ -41,8 +41,7 @@
           [&run_loop, this](std::unique_ptr<Tracker> tracker) {
             std::optional<net::IPAddress> address =
                 net::IPAddress::FromIPLiteral("192.168.1.18");
-            net::IPEndPoint end_point(*address, 80);
-            tracker->OnBeforeConnect(end_point);
+            tracker->SetServerAddress(*address);
             trackers_.push(std::move(tracker));
             run_loop.Quit();
           }));
@@ -72,8 +71,7 @@
             std::optional<net::IPAddress> address =
                 net::IPAddress::FromIPLiteral("192.168.1." +
                                               base::NumberToString(i + 1));
-            net::IPEndPoint end_point(*address, 80);
-            tracker->OnBeforeConnect(end_point);
+            tracker->SetServerAddress(*address);
             trackers_.push(std::move(tracker));
             run_loop.Quit();
           }));
@@ -87,8 +85,8 @@
       base::RunLoop run_loop;
       auto result = context().PerformThrottle(base::BindLambdaForTesting(
           [&run_loop, this](std::unique_ptr<Tracker> tracker) {
-            net::IPEndPoint end_point;
-            tracker->OnBeforeConnect(end_point);
+            net::IPAddress address;
+            tracker->SetServerAddress(address);
             trackers_.push(std::move(tracker));
             run_loop.Quit();
           }));
@@ -115,9 +113,12 @@
     }
   }
 
-  void CloseAbruptly(int count) {
+  void CloseAbruptly(int count, bool throttle_done) {
     DCHECK_LE(count, static_cast<int>(trackers_.size()));
     for (int i = 0; i < count; ++i) {
+      if (throttle_done) {
+        trackers_.front()->set_throttle_done();
+      }
       trackers_.pop();
     }
   }
@@ -145,7 +146,10 @@
   bool called() const { return called_; }
 
  private:
-  void OnCall(std::unique_ptr<Tracker> tracker) { called_ = true; }
+  void OnCall(std::unique_ptr<Tracker> tracker) {
+    called_ = true;
+    tracker->set_throttle_done();
+  }
 
   bool called_ = false;
 };
@@ -245,10 +249,40 @@
   EXPECT_TRUE(callback.called());
 }
 
+TEST_F(WebTransportThrottleContextTest,
+       CancelledBeforeThrottlingAvoidsPenalty) {
+  CreatePendingWithoutConnect(2);
+  CloseAbruptly(2, /*throttle_done=*/false);
+
+  // The queue should be empty, so the callback is fired immediately.
+  CallTrackingCallback callback;
+  const auto result = context().PerformThrottle(callback.Callback());
+  EXPECT_EQ(result, ThrottleResult::kOk);
+  EXPECT_TRUE(callback.called());
+}
+
+TEST_F(WebTransportThrottleContextTest,
+       CancelledBeforeHandshakeRemainsPendingFor100ms) {
+  CreatePending(1);
+
+  CloseAbruptly(1, /*throttle_done=*/true);
+
+  // The delay should be less than 100ms.
+  FastForwardBy(base::Milliseconds(99));
+  CallTrackingCallback callback;
+  const auto result = context().PerformThrottle(callback.Callback());
+  EXPECT_EQ(result, ThrottleResult::kOk);
+  EXPECT_FALSE(callback.called());
+
+  // The delay should be more than 100ms.
+  FastForwardBy(base::Milliseconds(2));
+  EXPECT_TRUE(callback.called());
+}
+
 TEST_F(WebTransportThrottleContextTest, CancelledOnceRemainsPendingFor50ms) {
   CreatePendingWithoutConnect(1);
 
-  CloseAbruptly(1);
+  CloseAbruptly(1, /*throttle_done=*/true);
 
   // The delay should be more than 99ms.
   FastForwardBy(base::Milliseconds(49));
@@ -265,7 +299,7 @@
 TEST_F(WebTransportThrottleContextTest, CancelledRemainsPendingFor5m) {
   CreatePendingWithoutConnect(2);
 
-  CloseAbruptly(2);
+  CloseAbruptly(2, /*throttle_done=*/true);
 
   // The delay should be more than 299 seconds.
   FastForwardBy(base::Seconds(299));
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 3dab3a53..4a34802 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -389,6 +389,11 @@
 #endif
 );
 
+// Enables fast-shutdown to ignore workers during urgent discards on certain
+// platforms.
+const base::FeatureParam<bool> kUrgentDiscardIgnoreWorkers{
+    &kWebContentsDiscard, "urgent_discard_ignore_workers", false};
+
 // When this feature is enabled, partial storage cleanup will be
 // disabled for the GPU disk cache. (Performance improvement)
 BASE_FEATURE(kDisablePartialStorageCleanupForGPUDiskCache,
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 1b811bc8..6c97305b8 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -119,6 +119,8 @@
     kBtmClientBounceDetectionTimeout;
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kBtmDualUse);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebContentsDiscard);
+CONTENT_EXPORT extern const base::FeatureParam<bool>
+    kUrgentDiscardIgnoreWorkers;
 CONTENT_EXPORT BASE_DECLARE_FEATURE(
     kDisablePartialStorageCleanupForGPUDiskCache);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kDrawCutoutEdgeToEdge);
diff --git a/docs/clang_tool_refactoring.md b/docs/clang_tool_refactoring.md
index beb0acd..26d921f 100644
--- a/docs/clang_tool_refactoring.md
+++ b/docs/clang_tool_refactoring.md
@@ -228,7 +228,7 @@
 that is to `return 1` from the `main()` function of the clang tool.
 
 ## Testing
-Synposis:
+Synopsis:
 
 ```shell
 tools/clang/scripts/test_tool.py <tool name> [--apply-edits]
diff --git a/gpu/command_buffer/service/gpu_persistent_cache.cc b/gpu/command_buffer/service/gpu_persistent_cache.cc
index dbadf78..afa52094 100644
--- a/gpu/command_buffer/service/gpu_persistent_cache.cc
+++ b/gpu/command_buffer/service/gpu_persistent_cache.cc
@@ -432,11 +432,16 @@
     return base::span<uint8_t>();
   };
 
-  if (!LoadImpl(key_str, std::move(buffer_provider))) {
-    return 0;  // Cache miss or error.
+  CacheLoadResult result = LoadImpl(key_str, std::move(buffer_provider));
+  if (!IsCacheHitResult(result) || value_size == 0) {
+    // This function is called twice in the cache hit case, once to query the
+    // size of the buffer and again with a buffer to write into. To avoid
+    // skewing the metrics by generating two cache hit data points, only record
+    // a cache hit when there is no buffer provided.
+    RecordCacheLoadResultHistogram(result);
   }
 
-  return discovered_size;
+  return static_cast<GLsizeiptr>(discovered_size);
 }
 
 sk_sp<SkData> GpuPersistentCache::load(const SkData& key) {
@@ -454,11 +459,9 @@
                    output_data->size()));
   };
 
-  if (!LoadImpl(key_str, std::move(buffer_provider))) {
-    return nullptr;  // Cache miss or error.
-  }
+  CacheLoadResult result = LoadImpl(key_str, std::move(buffer_provider));
+  RecordCacheLoadResultHistogram(result);
 
-  // Cache hit. The content is in `output_data`.
   return output_data;
 }
 
@@ -488,11 +491,16 @@
         return base::span<uint8_t>();
       };
 
-  if (!LoadImpl(key_str, std::move(buffer_provider))) {
-    return 0;  // Cache miss or error.
+  CacheLoadResult result = LoadImpl(key_str, std::move(buffer_provider));
+  if (!IsCacheHitResult(result) || value_size == 0) {
+    // This function is called twice in the cache hit case, once to query the
+    // size of the buffer and again with a buffer to write into. To avoid
+    // skewing the metrics by generating two cache hit data points, only record
+    // a cache hit when there is no buffer provided.
+    RecordCacheLoadResultHistogram(result);
   }
 
-  return static_cast<GLsizeiptr>(discovered_size);
+  return discovered_size;
 }
 
 void GpuPersistentCache::PurgeMemory(
@@ -515,7 +523,11 @@
   return disk_cache_->persistent_cache();
 }
 
-bool GpuPersistentCache::LoadImpl(
+bool GpuPersistentCache::IsCacheHitResult(CacheLoadResult result) {
+  return result > CacheLoadResult::kMaxMissValue;
+}
+
+GpuPersistentCache::CacheLoadResult GpuPersistentCache::LoadImpl(
     std::string_view key,
     persistent_cache::BufferProvider buffer_provider) {
   const bool disk_cache_initialized = disk_cache_initialized_.IsSet();
@@ -535,12 +547,13 @@
     if (auto memory_entry = memory_cache_->Find(key)) {
       base::span<uint8_t> output_buffer =
           buffer_provider(memory_entry->DataSize());
-      return memory_entry->ReadData(output_buffer.data(), output_buffer.size());
+      memory_entry->ReadData(output_buffer.data(), output_buffer.size());
+      return CacheLoadResult::kHitMemory;
     }
   }
 
   if (!disk_cache_initialized) {
-    return false;
+    return CacheLoadResult::kMissNoDiskCache;
   }
 
   base::span<uint8_t> provided_buffer;
@@ -575,7 +588,7 @@
       };
 
   if (!disk_cache_->Load(key, wrapped_buffer_provider)) {
-    return false;
+    return CacheLoadResult::kMiss;
   }
 
   if (memory_cache_) {
@@ -595,7 +608,7 @@
     }
   }
 
-  return true;
+  return CacheLoadResult::kHitDisk;
 }
 
 void GpuPersistentCache::StoreData(const void* key,
@@ -661,6 +674,12 @@
   disk_cache_->Store(memory_cache_entry);
 }
 
+void GpuPersistentCache::RecordCacheLoadResultHistogram(
+    CacheLoadResult result) {
+  base::UmaHistogramEnumeration(GetHistogramName(cache_prefix_, "LoadResult"),
+                                result);
+}
+
 void BindCacheToCurrentOpenGLContext(GpuPersistentCache* cache) {
   if (!cache || !gl::g_current_gl_driver->ext.b_GL_ANGLE_blob_cache) {
     return;
diff --git a/gpu/command_buffer/service/gpu_persistent_cache.h b/gpu/command_buffer/service/gpu_persistent_cache.h
index 4b11e3d..9b0c716 100644
--- a/gpu/command_buffer/service/gpu_persistent_cache.h
+++ b/gpu/command_buffer/service/gpu_persistent_cache.h
@@ -103,10 +103,25 @@
  private:
   struct DiskCache;
 
-  bool LoadImpl(std::string_view key,
-                persistent_cache::BufferProvider buffer_provider);
+  // Values are mirrored in tools/metrics/histograms/metadata/gpu/enums.xml
+  enum class CacheLoadResult {
+    kMiss = 0,
+    kMissNoDiskCache = 1,
+    kMaxMissValue = kMissNoDiskCache,
+    // Extra enum space for future miss results
+    kHitMemory = 10,
+    kHitDisk = 11,
+    kMaxValue = kHitDisk,
+  };
+
+  static bool IsCacheHitResult(CacheLoadResult result);
+
+  CacheLoadResult LoadImpl(std::string_view key,
+                           persistent_cache::BufferProvider buffer_provider);
   void StoreImpl(std::string_view key, base::span<const uint8_t> value);
 
+  void RecordCacheLoadResultHistogram(CacheLoadResult result);
+
   // Prefix to prepend to UMA histogram's name. e.g GraphiteDawn, WebGPU
   const std::string cache_prefix_;
 
diff --git a/gpu/command_buffer/service/shared_image/external_vk_image_backing.cc b/gpu/command_buffer/service/shared_image/external_vk_image_backing.cc
index 667e1dd..5cf12c3 100644
--- a/gpu/command_buffer/service/shared_image/external_vk_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/external_vk_image_backing.cc
@@ -833,7 +833,7 @@
                                memory_fd.release());
 #elif BUILDFLAG(IS_WIN)
     auto memory_handle = vulkan_image->GetMemoryHandle();
-    if (!memory_handle.IsValid()) {
+    if (!memory_handle.is_valid()) {
       return false;
     }
     memory_object.emplace(api);
diff --git a/gpu/vulkan/vulkan_image_unittest.cc b/gpu/vulkan/vulkan_image_unittest.cc
index 433cb8d7..64fddbe 100644
--- a/gpu/vulkan/vulkan_image_unittest.cc
+++ b/gpu/vulkan/vulkan_image_unittest.cc
@@ -105,7 +105,7 @@
         continue;
       base::win::ScopedHandle scoped_handle = image->GetMemoryHandle(
           static_cast<VkExternalMemoryHandleTypeFlagBits>(handle_type));
-      EXPECT_TRUE(scoped_handle.IsValid())
+      EXPECT_TRUE(scoped_handle.is_valid())
           << std::hex << " handle_types = 0x" << image->handle_types()
           << " handle_type = 0x" << handle_type;
     }
diff --git a/infra/config/generated/builders/ci/ios-blink-rel-fyi/properties.json b/infra/config/generated/builders/ci/ios-blink-rel-fyi/properties.json
index 7fadf69..03087ce 100644
--- a/infra/config/generated/builders/ci/ios-blink-rel-fyi/properties.json
+++ b/infra/config/generated/builders/ci/ios-blink-rel-fyi/properties.json
@@ -70,5 +70,5 @@
   },
   "builder_group": "chromium.fyi",
   "recipe": "chromium",
-  "xcode_build_version": "17c5013i"
+  "xcode_build_version": "17a400"
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/ios-blink-rel-fyi/properties.json b/infra/config/generated/builders/try/ios-blink-rel-fyi/properties.json
index 8210195..24540bd 100644
--- a/infra/config/generated/builders/try/ios-blink-rel-fyi/properties.json
+++ b/infra/config/generated/builders/try/ios-blink-rel-fyi/properties.json
@@ -63,5 +63,5 @@
   },
   "builder_group": "tryserver.chromium.mac",
   "recipe": "chromium_trybot",
-  "xcode_build_version": "17c5013i"
+  "xcode_build_version": "17a400"
 }
\ No newline at end of file
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 0b8a194b..32b7ae1 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -53916,8 +53916,8 @@
       priority: 35
       execution_timeout_secs: 10800
       caches {
-        name: "xcode_ios_17c5013i"
-        path: "xcode_ios_17c5013i.app"
+        name: "xcode_ios_17a400"
+        path: "xcode_ios_17a400.app"
       }
       build_numbers: YES
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -109962,8 +109962,8 @@
         seconds: 120
       }
       caches {
-        name: "xcode_ios_17c5013i"
-        path: "xcode_ios_17c5013i.app"
+        name: "xcode_ios_17a400"
+        path: "xcode_ios_17a400.app"
       }
       build_numbers: YES
       service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index 3b2306e..c96866a 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -1734,7 +1734,6 @@
         short_name = "ios-blk",
     ),
     execution_timeout = 3 * time.hour,
-    xcode = xcode.x26betabots,
 )
 
 fyi_ios_builder(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
index 64d7c48..bca366e 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
@@ -593,7 +593,6 @@
     builderless = True,
     cpu = cpu.ARM64,
     execution_timeout = 4 * time.hour,
-    xcode = xcode.x26betabots,
 )
 
 ios_builder(
diff --git a/ios/chrome/app/BUILD.gn b/ios/chrome/app/BUILD.gn
index c0d4b47..e2168f0 100644
--- a/ios/chrome/app/BUILD.gn
+++ b/ios/chrome/app/BUILD.gn
@@ -715,6 +715,8 @@
     orderfile_path = ios_chrome_orderfile
   }
 
+  link_framework_first = "EarlyMallocZoneRegistration"
+
   bundle_deps = [
     "//ios/chrome/app/resources",
     "//ios/chrome/app/resources:settings_resources",
diff --git a/ios/chrome/app/startup/ios_chrome_main.mm b/ios/chrome/app/startup/ios_chrome_main.mm
index ef8e59e2..0d53314c 100644
--- a/ios/chrome/app/startup/ios_chrome_main.mm
+++ b/ios/chrome/app/startup/ios_chrome_main.mm
@@ -20,23 +20,12 @@
 
 IOSChromeMain::IOSChromeMain() {
   web::WebMainParams main_params(&main_delegate_);
-  NSArray* arguments = [[NSProcessInfo processInfo] arguments];
-  main_params.argc = [arguments count];
-  base::FixedArray<const char*> argv(main_params.argc);
-  std::vector<std::string> argv_store;
+  NSArray<NSString*>* arguments = [[NSProcessInfo processInfo] arguments];
 
-  // Avoid using std::vector::push_back (or any other method that could cause
-  // the vector to grow) as this will cause the std::string to be copied or
-  // moved (depends on the C++ implementation) which may invalidates the pointer
-  // returned by std::string::c_str(). Even if the strings are moved, this may
-  // cause garbage if std::string uses optimisation for small strings (by
-  // returning pointer to the object internals in that case).
-  argv_store.resize([arguments count]);
-  for (NSUInteger i = 0; i < [arguments count]; i++) {
-    argv_store[i] = base::SysNSStringToUTF8([arguments objectAtIndex:i]);
-    argv[i] = argv_store[i].c_str();
+  main_params.args.reserve([arguments count]);
+  for (NSString* argument in arguments) {
+    main_params.args.push_back(base::SysNSStringToUTF8(argument));
   }
-  main_params.argv = argv.data();
 
   // Chrome registers an AtExitManager in main in order to initialize the crash
   // handler early, so prevent a second registration by WebMainRunner.
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 0b7d7b08..b7bf04e6 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -1623,6 +1623,9 @@
       <message name="IDS_IOS_CONTENT_SUGGESTIONS_ACCESSIBILITY_LABEL_SUGGESTION" desc="The accessibility label of a suggestion. Summarizes fields in the reading list entry (title, publisher informations, status and informations). Read by Text-to-Speech.">
         <ph name="TITLE"><ex>Learn about the new Chromium projects</ex>$1</ph>, <ph name="PUBLISHER_INFORMATION"><ex>The Chromium organization</ex>$2</ph>, <ph name="PUBLICATION_DATE"><ex>January 1 2017</ex>$3</ph>
       </message>
+      <message name="IDS_IOS_CONTENT_SUGGESTIONS_ADD_PINNED_SITE" desc="The title of the button to add a pinned site on the new tab page [Length: 10em]">
+        Add Site
+      </message>
       <message name="IDS_IOS_CONTENT_SUGGESTIONS_BOOKMARKS" desc="The Bookmarks title on the new tab page [Length: 10em]">
         Bookmarks
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_CONTENT_SUGGESTIONS_ADD_PINNED_SITE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_CONTENT_SUGGESTIONS_ADD_PINNED_SITE.png.sha1
new file mode 100644
index 0000000..a7b5df8
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_CONTENT_SUGGESTIONS_ADD_PINNED_SITE.png.sha1
@@ -0,0 +1 @@
+3b4893842f5239eee3a559fd32444a3017008683
\ No newline at end of file
diff --git a/ios/chrome/browser/autofill/model/autofill_controller_js_unittest.mm b/ios/chrome/browser/autofill/model/autofill_controller_js_unittest.mm
index 298638f..3d0bb22c 100644
--- a/ios/chrome/browser/autofill/model/autofill_controller_js_unittest.mm
+++ b/ios/chrome/browser/autofill/model/autofill_controller_js_unittest.mm
@@ -866,7 +866,7 @@
                                       NSArray* test_data,
                                       NSString* tag_name);
 
-  // Helper method that EXPECTs `__gCrWeb.fill.webFormElementToFormData` on
+  // Helper method that EXPECTs `webFormElementToFormData` on
   // a form element obtained by `get_form_element_javascripts`. The results
   // are verified with `verifying_java_scripts`.
   void TestWebFormElementToFormDataForOneForm(
@@ -901,6 +901,11 @@
 
   id ExecuteJavaScript(NSString* java_script);
 
+  // Rolls up the `user_script`, and its dependencies, with `java_script` into
+  // one NSString.
+  NSString* RollupJavaScriptWithUserScript(NSString* java_script,
+                                           NSString* user_script);
+
   web::ScopedTestingWebClient web_client_;
   web::WebTaskEnvironment task_environment_;
   std::unique_ptr<TestProfileIOS> profile_;
@@ -986,6 +991,18 @@
       autofill::AutofillJavaScriptFeature::GetInstance());
 }
 
+NSString* AutofillControllerJsTest::RollupJavaScriptWithUserScript(
+    NSString* java_script,
+    NSString* user_script) {
+  NSArray<NSString*>* user_scripts = @[ @"gcrweb", user_script ];
+  NSMutableString* rollup_script = [NSMutableString string];
+  for (NSString* script in user_scripts) {
+    [rollup_script appendString:web::test::GetPageScript(script)];
+  }
+  [rollup_script appendString:java_script];
+  return rollup_script;
+}
+
 TEST_F(AutofillControllerJsTest, HasTagName) {
   constexpr auto kElementsExpectingTrue = std::to_array<ElementByName>({
       {"hl", 0, -1},
@@ -1513,25 +1530,29 @@
     NSString* get_form_element_javascripts,
     NSString* expected_result,
     NSString* verifying_javascripts) {
-  NSString* actual = ExecuteJavaScript(
+  NSString* java_script =
       [NSString stringWithFormat:@"var form={}; var field={};"
-                                  "(__gCrWeb.fill.webFormElementToFormData("
-                                  "window, %@, null, form, field) "
+                                 @"(__gCrWeb.getRegisteredApi('fill_test_api')."
+                                 @"getFunction('webFormElementToFormData')"
+                                 @"(window, %@, null, form, field) "
                                   "=== %@) && %@",
                                  get_form_element_javascripts, expected_result,
-                                 verifying_javascripts]);
+                                 verifying_javascripts];
+  NSString* script =
+      RollupJavaScriptWithUserScript(java_script, @"fill_util_test");
+  NSString* actual = ExecuteJavaScript(script);
 
+  java_script =
+      [NSString stringWithFormat:@"var form={};"
+                                 @"__gCrWeb.getRegisteredApi('fill_test_api')."
+                                 @"getFunction('webFormElementToFormData')"
+                                 @"(window, %@, null, form, null);"
+                                  "__gCrWeb.stringify(form);",
+                                 get_form_element_javascripts];
+  script = RollupJavaScriptWithUserScript(java_script, @"fill_util_test");
   EXPECT_NSEQ(@YES, actual) << base::SysNSStringToUTF8([NSString
-      stringWithFormat:
-          @"Actual:\n%@; expected to be verifyied by\n%@",
-          ExecuteJavaScript([NSString
-              stringWithFormat:@"var form={};"
-                                "__gCrWeb.fill."
-                                "webFormElementToFormData(window, %@, null,"
-                                "form, null);"
-                                "__gCrWeb.stringify(form);",
-                               get_form_element_javascripts]),
-          verifying_javascripts]);
+      stringWithFormat:@"Actual:\n%@; expected to be verifyied by\n%@",
+                       ExecuteJavaScript(script), verifying_javascripts]);
 }
 
 void AutofillControllerJsTest::TestWebFormElementToFormData(
diff --git a/ios/chrome/browser/badges/ui_bundled/badge_button_factory.mm b/ios/chrome/browser/badges/ui_bundled/badge_button_factory.mm
index 48c86f5c..1be4bc7 100644
--- a/ios/chrome/browser/badges/ui_bundled/badge_button_factory.mm
+++ b/ios/chrome/browser/badges/ui_bundled/badge_button_factory.mm
@@ -178,8 +178,13 @@
 }
 
 - (BadgeButton*)overflowBadgeButton {
-  UIImage* image = DefaultSymbolWithPointSize(kEllipsisCircleFillSymbol,
-                                              [self infoBarSymbolPointSize]);
+  NSString* symbolName = IsProactiveSuggestionsFrameworkEnabled()
+                             ? kEllipsisSymbol
+                             : kEllipsisCircleFillSymbol;
+
+  UIImage* image =
+      DefaultSymbolWithPointSize(symbolName, [self infoBarSymbolPointSize]);
+
   if (IsProactiveSuggestionsFrameworkEnabled()) {
     image = [image imageWithTintColor:[UIColor whiteColor]
                         renderingMode:UIImageRenderingModeAlwaysOriginal];
diff --git a/ios/chrome/browser/composebox/coordinator/composebox_input_plate_coordinator.mm b/ios/chrome/browser/composebox/coordinator/composebox_input_plate_coordinator.mm
index 37684031..278d8c2 100644
--- a/ios/chrome/browser/composebox/coordinator/composebox_input_plate_coordinator.mm
+++ b/ios/chrome/browser/composebox/coordinator/composebox_input_plate_coordinator.mm
@@ -20,6 +20,7 @@
 #import "ios/chrome/browser/composebox/public/features.h"
 #import "ios/chrome/browser/composebox/ui/composebox_input_plate_view_controller.h"
 #import "ios/chrome/browser/composebox/ui/composebox_metrics_recorder.h"
+#import "ios/chrome/browser/composebox/ui/composebox_snackbar_presenter.h"
 #import "ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h"
 #import "ios/chrome/browser/feature_engagement/model/tracker_factory.h"
 #import "ios/chrome/browser/intelligence/persist_tab_context/model/persist_tab_context_browser_agent.h"
@@ -58,6 +59,7 @@
 
 namespace {
 const size_t kMaxURLDisplayChars = 32 * 1024;
+const CGFloat kSnackbarBottomMargin = 10;
 }
 
 @interface ComposeboxInputPlateCoordinator () <
@@ -266,6 +268,10 @@
     (ComposeboxInputPlateViewController*)composeboxViewController {
   [_metricsRecorder
       recordAttachmentButtonUsed:FuseboxAttachmentButtonType::kGallery];
+  if (![_mediator canAddMoreAttachments]) {
+    [self showMaxAttachmentSnackbarError];
+    return;
+  }
   if (!_picker) {
     [self
         composeboxViewControllerMayShowGalleryPicker:composeboxViewController];
@@ -278,6 +284,10 @@
     (ComposeboxInputPlateViewController*)composeboxViewController {
   [_metricsRecorder
       recordAttachmentButtonUsed:FuseboxAttachmentButtonType::kCamera];
+  if (![_mediator canAddMoreAttachments]) {
+    [self showMaxAttachmentSnackbarError];
+    return;
+  }
   if (![UIImagePickerController
           isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
     // TODO(crbug.com/40280872): Show an error to the user.
@@ -307,6 +317,10 @@
     (ComposeboxInputPlateViewController*)composeboxViewController {
   [_metricsRecorder
       recordAttachmentButtonUsed:FuseboxAttachmentButtonType::kFiles];
+  if (![_mediator canAddMoreAttachments]) {
+    [self showMaxAttachmentSnackbarError];
+    return;
+  }
   UIDocumentPickerViewController* picker =
       [[UIDocumentPickerViewController alloc]
           initForOpeningContentTypes:@[ UTTypePDF ]];
@@ -317,6 +331,10 @@
 
 - (void)composeboxViewControllerDidTapAttachTabsButton:
     (ComposeboxInputPlateViewController*)viewController {
+  if (![_mediator canAddMoreAttachments]) {
+    [self showMaxAttachmentSnackbarError];
+    return;
+  }
   [self showComposeboxTabPicker];
 }
 
@@ -374,6 +392,10 @@
   [_omniboxCoordinator clearSuggestionsAndRestartAutocomplete];
 }
 
+- (void)showAttachmentLimitError {
+  [self showMaxAttachmentSnackbarError];
+}
+
 #pragma mark - LocationBarURLLoader
 
 - (void)loadGURLFromLocationBar:(const GURL&)url
@@ -447,12 +469,25 @@
   _tabPickerCoordinator = nil;
 }
 
-#pragma mark - Private
+#pragma mark - Private helpers
 
+/// Dismisses the composebox via a command to the browser coordinator.
 - (void)dismissComposebox {
   id<BrowserCoordinatorCommands> commands = HandlerForProtocol(
       self.browser->GetCommandDispatcher(), BrowserCoordinatorCommands);
   [commands hideComposeboxImmediately:NO];
 }
 
+/// Displays a snackbar error indicating the maximum number of attachments has
+/// been reached.
+- (void)showMaxAttachmentSnackbarError {
+  ComposeboxSnackbarPresenter* snackbar =
+      [[ComposeboxSnackbarPresenter alloc] initWithBrowser:self.browser];
+  CGFloat offset = _viewController.keyboardHeight;
+  if (!_theme.isTopInputPlate) {
+    offset += _viewController.inputHeight + kSnackbarBottomMargin;
+  }
+  [snackbar showAttachmentLimitSnackbarWithBottomOffset:offset];
+}
+
 @end
diff --git a/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator.h b/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator.h
index 65d616ef..190a4e6d 100644
--- a/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator.h
+++ b/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator.h
@@ -29,6 +29,8 @@
 @protocol ComposeboxInputPlateMediatorDelegate
 // Reloads the composebox autocomplete suggestions.
 - (void)reloadAutocompleteSuggestions;
+// Informs the delegate that adding an attachment failed due to limit.
+- (void)showAttachmentLimitError;
 @end
 
 // Mediator for the composebox composebox.
@@ -65,6 +67,9 @@
 // Processes the given `PDFFileURL` for a file.
 - (void)processPDFFileURL:(GURL)PDFFileURL;
 
+// Returns whether more attachments can be added.
+- (BOOL)canAddMoreAttachments;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_COMPOSEBOX_COORDINATOR_COMPOSEBOX_INPUT_PLATE_MEDIATOR_H_
diff --git a/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator.mm b/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator.mm
index ee44a6c..010fb07 100644
--- a/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator.mm
+++ b/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator.mm
@@ -34,6 +34,7 @@
 #import "components/omnibox/composebox/ios/composebox_query_controller_ios.h"
 #import "components/search_engines/template_url_service.h"
 #import "components/search_engines/util.h"
+#import "ios/chrome/browser/composebox/coordinator/composebox_constants.h"
 #import "ios/chrome/browser/composebox/coordinator/composebox_url_loader.h"
 #import "ios/chrome/browser/composebox/coordinator/web_state_deferred_executor.h"
 #import "ios/chrome/browser/composebox/public/features.h"
@@ -294,6 +295,11 @@
       }));
 }
 
+- (BOOL)canAddMoreAttachments {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(_sequenceChecker);
+  return _items.count < kAttachmentLimit;
+}
+
 #pragma mark - ComposeboxInputPlateMutator
 
 - (void)removeItem:(ComposeboxInputItem*)item {
@@ -591,6 +597,10 @@
 
 - (void)attachCurrentTabContent {
   DCHECK_CALLED_ON_VALID_SEQUENCE(_sequenceChecker);
+  if (![self canAddMoreAttachments]) {
+    [self.delegate showAttachmentLimitError];
+    return;
+  }
   web::WebState* webState = _webStateList->GetActiveWebState();
   if (!webState) {
     return;
diff --git a/ios/chrome/browser/composebox/ui/composebox_input_plate_view_controller.h b/ios/chrome/browser/composebox/ui/composebox_input_plate_view_controller.h
index bf38844..7fae072 100644
--- a/ios/chrome/browser/composebox/ui/composebox_input_plate_view_controller.h
+++ b/ios/chrome/browser/composebox/ui/composebox_input_plate_view_controller.h
@@ -58,6 +58,7 @@
 
 /// Height of the input view.
 @property(nonatomic, readonly) CGFloat inputHeight;
+@property(nonatomic, readonly) CGFloat keyboardHeight;
 
 // The input plate view to be used in animations.
 @property(nonatomic, readonly) UIView* inputPlateViewForAnimation;
diff --git a/ios/chrome/browser/composebox/ui/composebox_input_plate_view_controller.mm b/ios/chrome/browser/composebox/ui/composebox_input_plate_view_controller.mm
index decfb447..8bf0863a 100644
--- a/ios/chrome/browser/composebox/ui/composebox_input_plate_view_controller.mm
+++ b/ios/chrome/browser/composebox/ui/composebox_input_plate_view_controller.mm
@@ -19,6 +19,7 @@
 #import "ios/chrome/browser/composebox/ui/composebox_input_item_view.h"
 #import "ios/chrome/browser/composebox/ui/composebox_input_plate_mutator.h"
 #import "ios/chrome/browser/composebox/ui/composebox_metrics_recorder.h"
+#import "ios/chrome/browser/composebox/ui/composebox_snackbar_presenter.h"
 #import "ios/chrome/browser/omnibox/ui/text_field_view_containing.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/chrome/browser/shared/ui/elements/extended_touch_target_button.h"
@@ -90,7 +91,6 @@
 const CGFloat kCloseIndicatorSize = 10.0f;
 }  // namespace
 
-
 @interface ComposeboxInputPlateViewController () <
     UITextViewDelegate,
     ComposeboxInputItemCellDelegate,
@@ -163,12 +163,23 @@
 
 /// ComposeboxAnimationContextProvider
 @synthesize inputPlateViewForAnimation = _inputPlateContainerView;
+@synthesize keyboardHeight = _keyboardHeight;
 
 - (instancetype)initWithTheme:(ComposeboxTheme*)theme {
   self = [super init];
   if (self) {
     _omniboxContainer = [[UIView alloc] init];
     _theme = theme;
+    [[NSNotificationCenter defaultCenter]
+        addObserver:self
+           selector:@selector(keyboardWillShow:)
+               name:UIKeyboardWillShowNotification
+             object:nil];
+    [[NSNotificationCenter defaultCenter]
+        addObserver:self
+           selector:@selector(keyboardWillHide:)
+               name:UIKeyboardWillHideNotification
+             object:nil];
   }
   return self;
 }
@@ -230,6 +241,10 @@
   return _inputPlateContainerView.frame.size.height;
 }
 
+- (CGFloat)keyboardHeight {
+  return _keyboardHeight;
+}
+
 - (void)setEditView:(UIView<TextFieldViewContaining>*)editView {
   _editView = editView;
   _editView.translatesAutoresizingMaskIntoConstraints = NO;
@@ -410,81 +425,6 @@
   return composeboxAttachments::kTabFileInputItemSize;
 }
 
-#pragma mark - Private
-
-- (void)handleAttachTabs {
-  [self.delegate composeboxViewControllerDidTapAttachTabsButton:self];
-}
-
-- (void)handleAIMTappedFromToolMenu {
-  self.AIModeEnabled = !self.AIModeEnabled;
-  if (self.AIModeEnabled) {
-    [self.metricsRecorder
-        recordAiModeActivationSource:AiModeActivationSource::kToolMenu];
-  }
-}
-
-- (void)updateCarouselFade {
-  CGFloat contentOffsetX = _carouselView.contentOffset.x;
-  CGFloat contentWidth = _carouselView.contentSize.width;
-  CGFloat boundsWidth = _carouselView.bounds.size.width;
-
-  _leadingCarouselFadeView.hidden = contentOffsetX <= 0;
-  _trailingCarouselFadeView.hidden =
-      contentOffsetX + boundsWidth >= contentWidth;
-}
-
-- (void)setAIModeEnabled:(BOOL)AIModeEnabled {
-  if (AIModeEnabled == _AIModeEnabled) {
-    return;
-  }
-  _AIModeEnabled = AIModeEnabled;
-  [self updateAIMButtonAppearance];
-  [self updatePlusButtonItems];
-  [self.mutator setAIModeEnabled:_AIModeEnabled];
-  [self triggerGlowEffect];
-}
-
-- (void)triggerGlowEffect {
-  if (!_glowEffectView) {
-    return;
-  }
-
-  // Cancel any previously scheduled updates.
-  _updateGlowCallback.Cancel();
-
-  if (_AIModeEnabled) {
-    // When turning on, ensure the glow is started. The view's state machine
-    // will prevent it from restarting if it's already active.
-    [_glowEffectView startGlow];
-  } else if (_glowEffectView.glowState == GlowState::kStoppingRotation) {
-    // If the user toggles off while the rotation is already stopping, stop the
-    // glow immediately.
-    [_glowEffectView stopGlow];
-    return;
-  }
-
-  // Schedule the next state transition after the delay, regardless of whether
-  // the mode was turned on or off.
-  __weak __typeof__(self) weakSelf = self;
-  _updateGlowCallback.Reset(base::BindOnce(^{
-    [weakSelf updateGlow];
-  }));
-  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
-      FROM_HERE, _updateGlowCallback.callback(),
-      base::Seconds(kGlowEffectDuration));
-}
-
-/// Called after a delay to transition the glow effect to its next state.
-- (void)updateGlow {
-  [_glowEffectView stopGlow];
-}
-
-- (void)userInterfaceStyleChanged {
-  [self updateAIMButtonAppearance];
-  [self updateDepthShadowAppearance];
-}
-
 #pragma mark - UICollectionViewDelegate
 
 - (void)scrollViewDidScroll:(UIScrollView*)scrollView {
@@ -531,6 +471,97 @@
 
 #pragma mark - Private helpers
 
+/// Handles keyboard appearance notifications to adjust layout.
+- (void)keyboardWillShow:(NSNotification*)notification {
+  CGRect keyboardFrame =
+      [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
+  _keyboardHeight = keyboardFrame.size.height;
+}
+
+/// Handles keyboard disappearance notifications to reset layout.
+- (void)keyboardWillHide:(NSNotification*)notification {
+  _keyboardHeight = 0;
+}
+
+/// Notifies the delegate to show the tab attachment UI.
+- (void)handleAttachTabs {
+  [self.delegate composeboxViewControllerDidTapAttachTabsButton:self];
+}
+
+- (void)handleAIMTappedFromToolMenu {
+  self.AIModeEnabled = !self.AIModeEnabled;
+  if (self.AIModeEnabled) {
+    [self.metricsRecorder
+        recordAiModeActivationSource:AiModeActivationSource::kToolMenu];
+  }
+}
+
+/// Updates the visibility of the leading/trailing fade views for the carousel.
+- (void)updateCarouselFade {
+  CGFloat contentOffsetX = _carouselView.contentOffset.x;
+  CGFloat contentWidth = _carouselView.contentSize.width;
+  CGFloat boundsWidth = _carouselView.bounds.size.width;
+
+  _leadingCarouselFadeView.hidden = contentOffsetX <= 0;
+  _trailingCarouselFadeView.hidden =
+      contentOffsetX + boundsWidth >= contentWidth;
+}
+
+/// Enables or disables AI Mode and updates the UI accordingly.
+- (void)setAIModeEnabled:(BOOL)AIModeEnabled {
+  if (AIModeEnabled == _AIModeEnabled) {
+    return;
+  }
+  _AIModeEnabled = AIModeEnabled;
+  [self updateAIMButtonAppearance];
+  [self updatePlusButtonItems];
+  [self.mutator setAIModeEnabled:_AIModeEnabled];
+  [self triggerGlowEffect];
+}
+
+/// Initiates the glow animation around the input plate.
+- (void)triggerGlowEffect {
+  if (!_glowEffectView) {
+    return;
+  }
+
+  // Cancel any previously scheduled updates.
+  _updateGlowCallback.Cancel();
+
+  if (_AIModeEnabled) {
+    // When turning on, ensure the glow is started. The view's state machine
+    // will prevent it from restarting if it's already active.
+    [_glowEffectView startGlow];
+  } else if (_glowEffectView.glowState == GlowState::kStoppingRotation) {
+    // If the user toggles off while the rotation is already stopping, stop the
+    // glow immediately.
+    [_glowEffectView stopGlow];
+    return;
+  }
+
+  // Schedule the next state transition after the delay, regardless of whether
+  // the mode was turned on or off.
+  __weak __typeof__(self) weakSelf = self;
+  _updateGlowCallback.Reset(base::BindOnce(^{
+    [weakSelf updateGlow];
+  }));
+  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, _updateGlowCallback.callback(),
+      base::Seconds(kGlowEffectDuration));
+}
+
+/// Called after a delay to transition the glow effect to its next state.
+- (void)updateGlow {
+  [_glowEffectView stopGlow];
+}
+
+/// Responds to changes in the user interface style (e.g.: dark/light mode).
+- (void)userInterfaceStyleChanged {
+  [self updateAIMButtonAppearance];
+  [self updateDepthShadowAppearance];
+}
+
+/// Adjusts the shadow of the input plate based on UI style and theme.
 - (void)updateDepthShadowAppearance {
   if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark ||
       _theme.isTopInputPlate) {
@@ -606,6 +637,7 @@
   }
 }
 
+/// Adds and constraints the 'X' mark indicator to the AI Mode button.
 - (void)setupXMarkInAIMButton {
   [_aimButtonXIndicator removeFromSuperview];
 
@@ -714,6 +746,7 @@
   return lensButton;
 }
 
+/// Creates and returns the toolbar view containing action buttons.
 - (UIView*)createToolbarView {
   _aimButton = [UIButton buttonWithType:UIButtonTypeSystem];
   _aimButton.translatesAutoresizingMaskIntoConstraints = NO;
@@ -746,6 +779,7 @@
   return buttonsStackView;
 }
 
+/// Configures the menu items for the plus (+) button.
 - (void)updatePlusButtonItems {
   if (!_plusButton) {
     return;
@@ -842,6 +876,7 @@
   _plusButton.menu = [UIMenu menuWithTitle:@"" children:menuItems];
 }
 
+/// Initializes and configures the collection view for the attachment carousel.
 - (void)setupCarouselContainer {
   // Carousel view
   UICollectionViewFlowLayout* layout =
@@ -907,6 +942,7 @@
   ]];
 }
 
+/// Sets up the main container view for the input plate.
 - (void)setupInputPlateContainerView {
   _inputPlateContainerView = [[UIView alloc] init];
   _inputPlateContainerView.translatesAutoresizingMaskIntoConstraints = NO;
@@ -928,6 +964,8 @@
   }
 }
 
+/// Updates the content and layout of the input plate stack view based on the
+/// current mode (compact or expanded).
 - (void)updateInputPlateStackViewContent {
   for (UIView* arrangedSubview in _inputPlateStackView.arrangedSubviews) {
     if (arrangedSubview != _omniboxContainer) {
@@ -960,6 +998,8 @@
   }
 }
 
+/// Animates the transition of the input plate stack view between compact and
+/// expanded states.
 - (void)updateInputPlateStackViewAnimated:(BOOL)animated {
   if (!animated) {
     [self updateInputPlateStackViewContent];
@@ -1001,6 +1041,7 @@
                             completion:nil];
 }
 
+/// Generates a banana icon image to be used in the UI.
 - (UIImage*)bananaIcon {
   CGFloat iconPadding = 4.0;
   CGSize size = CGSizeMake(kSymbolActionPointSize + iconPadding,
diff --git a/ios/chrome/browser/composebox/ui/composebox_snackbar_presenter.h b/ios/chrome/browser/composebox/ui/composebox_snackbar_presenter.h
index 53baa50..75f9df3 100644
--- a/ios/chrome/browser/composebox/ui/composebox_snackbar_presenter.h
+++ b/ios/chrome/browser/composebox/ui/composebox_snackbar_presenter.h
@@ -15,6 +15,9 @@
 // Shows a snackbar with the attachment limit message.
 - (void)showAttachmentLimitSnackbar;
 
+// Shows a snackbar with the attachment limit message with a bottom offset.
+- (void)showAttachmentLimitSnackbarWithBottomOffset:(CGFloat)bottomOffset;
+
 - (instancetype)initWithBrowser:(Browser*)browser NS_DESIGNATED_INITIALIZER;
 - (instancetype)init NS_UNAVAILABLE;
 
diff --git a/ios/chrome/browser/composebox/ui/composebox_snackbar_presenter.mm b/ios/chrome/browser/composebox/ui/composebox_snackbar_presenter.mm
index 434ed4c..e14196e6 100644
--- a/ios/chrome/browser/composebox/ui/composebox_snackbar_presenter.mm
+++ b/ios/chrome/browser/composebox/ui/composebox_snackbar_presenter.mm
@@ -25,6 +25,10 @@
 }
 
 - (void)showAttachmentLimitSnackbar {
+  [self showAttachmentLimitSnackbarWithBottomOffset:0];
+}
+
+- (void)showAttachmentLimitSnackbarWithBottomOffset:(CGFloat)bottomOffset {
   NSString* title = l10n_util::GetPluralNSStringF(
       IDS_IOS_COMPOSEBOX_MAXIMUM_ATTACHMENTS_REACHED, kAttachmentLimit);
   SnackbarMessage* message = [[SnackbarMessage alloc] initWithTitle:title];
@@ -32,7 +36,7 @@
   CommandDispatcher* dispatcher = _browser->GetCommandDispatcher();
   id<SnackbarCommands> snackbarHandler =
       HandlerForProtocol(dispatcher, SnackbarCommands);
-  [snackbarHandler showSnackbarMessage:message bottomOffset:0];
+  [snackbarHandler showSnackbarMessage:message bottomOffset:bottomOffset];
 }
 
 @end
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/BUILD.gn b/ios/chrome/browser/content_suggestions/ui_bundled/cells/BUILD.gn
index 19021df1..51a34a8d 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/BUILD.gn
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/BUILD.gn
@@ -71,6 +71,8 @@
 
 source_set("most_visited_tiles") {
   sources = [
+    "content_suggestions_plus_button_item.h",
+    "content_suggestions_plus_button_item.mm",
     "content_suggestions_tile_saver.h",
     "content_suggestions_tile_saver.mm",
     "most_visited_tiles_collection_view.h",
@@ -85,6 +87,7 @@
   deps = [
     ":cells",
     ":constants",
+    ":most_visited_action",
     "//components/favicon/core",
     "//components/ntp_tiles",
     "//components/ntp_tiles:pref_names",
@@ -129,8 +132,8 @@
 
 source_set("shortcuts") {
   sources = [
-    "content_suggestions_most_visited_action_item.h",
-    "content_suggestions_most_visited_action_item.mm",
+    "content_suggestions_shortcut_item.h",
+    "content_suggestions_shortcut_item.mm",
     "content_suggestions_shortcut_tile_view.h",
     "content_suggestions_shortcut_tile_view.mm",
     "shortcuts_config.h",
@@ -139,8 +142,8 @@
     "shortcuts_mediator.mm",
   ]
   deps = [
-    ":cells",
     ":constants",
+    ":most_visited_action",
     ":public",
     "//components/feature_engagement/public",
     "//components/reading_list/core",
@@ -152,12 +155,27 @@
     "//ios/chrome/browser/content_suggestions/ui_bundled/magic_stack:public",
     "//ios/chrome/browser/feature_engagement/model",
     "//ios/chrome/browser/ntp/ui_bundled",
-    "//ios/chrome/browser/ntp/ui_bundled:theme",
     "//ios/chrome/browser/shared/public/commands",
     "//ios/chrome/browser/shared/public/features",
+    "//ios/chrome/browser/whats_new/coordinator:util",
+  ]
+}
+
+source_set("most_visited_action") {
+  sources = [
+    "content_suggestions_most_visited_action_item.h",
+    "content_suggestions_most_visited_action_item.mm",
+    "content_suggestions_most_visited_action_tile_view.h",
+    "content_suggestions_most_visited_action_tile_view.mm",
+  ]
+  deps = [
+    ":cells",
+    ":constants",
+    ":public",
+    "//ios/chrome/browser/ntp/ui_bundled:theme",
+    "//ios/chrome/browser/shared/public/features",
     "//ios/chrome/browser/shared/ui/symbols",
     "//ios/chrome/browser/shared/ui/util",
-    "//ios/chrome/browser/whats_new/coordinator:util",
     "//ios/chrome/common/ui/colors",
     "//ios/chrome/common/ui/util",
     "//ios/chrome/common/ui/util:dynamic_type_util",
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h
index f4a9b35..4ee28ab 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h
@@ -9,30 +9,22 @@
 
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_constants.h"
 
-// Item containing a most visited action button. These buttons belong to the
-// collection section as most visited items, but have static placement (the last
-// four) and cannot be removed.
+// Item containing a most visited action button.
 @interface ContentSuggestionsMostVisitedActionItem : NSObject
 
-- (nonnull instancetype)initWithCollectionShortcutType:
-    (NTPCollectionShortcutType)type;
+// Text for the title of the tile view.
+@property(nonatomic, strong, nonnull) NSString* title;
 
-// Text for the title of the cell.
-@property(nonatomic, copy, nonnull) NSString* title;
+// Image for the icon in the tile view.
+@property(nonatomic, strong, nonnull) UIImage* icon;
 
-// The accessibility label of the cell.  If none is provided, self.title is used
-// as the label.
-@property(nonatomic, copy, nullable) NSString* accessibilityLabel;
-
-// The collection that this item acts as a shortcut for.
-@property(nonatomic, assign) NTPCollectionShortcutType collectionShortcutType;
+// The accessibility label of the tile view.  If none is provided, self.title is
+// used as the label.
+@property(nonatomic, strong, nullable) NSString* accessibilityLabel;
 
 // Reading list count passed to the most visited cell.
 @property(nonatomic, assign) NSInteger count;
 
-// Index position of this item.
-@property(nonatomic, assign) NTPCollectionShortcutType index;
-
 // Indicate if this suggestion is (temporary) disabled.
 @property(nonatomic, assign) BOOL disabled;
 
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.mm b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.mm
index 2e721ee..4d397b2 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.mm
@@ -4,40 +4,8 @@
 
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h"
 
-#import "base/check.h"
-#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_constants.h"
-#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
-
 @implementation ContentSuggestionsMostVisitedActionItem
 
-- (instancetype)initWithCollectionShortcutType:(NTPCollectionShortcutType)type {
-  self = [super init];
-  if (self) {
-    _collectionShortcutType = type;
-    switch (_collectionShortcutType) {
-      case NTPCollectionShortcutTypeBookmark:
-        _index = NTPCollectionShortcutTypeBookmark;
-        break;
-      case NTPCollectionShortcutTypeReadingList:
-        _index = NTPCollectionShortcutTypeReadingList;
-        break;
-      case NTPCollectionShortcutTypeRecentTabs:
-        _index = NTPCollectionShortcutTypeRecentTabs;
-        break;
-      case NTPCollectionShortcutTypeHistory:
-        _index = NTPCollectionShortcutTypeHistory;
-        break;
-      case NTPCollectionShortcutTypeWhatsNew:
-        _index = NTPCollectionShortcutTypeWhatsNew;
-        break;
-      default:
-        break;
-    }
-    self.title = TitleForCollectionShortcutType(_collectionShortcutType);
-  }
-  return self;
-}
-
 #pragma mark - Accessors
 
 - (void)setTitle:(NSString*)title {
@@ -45,7 +13,7 @@
     return;
   }
   _title = title;
-  [self updateAccessibilityLabel];
+  [self updateAccessibilityTraits];
 }
 
 - (void)setCount:(NSInteger)count {
@@ -53,7 +21,7 @@
     return;
   }
   _count = count;
-  [self updateAccessibilityLabel];
+  [self updateAccessibilityTraits];
 }
 
 - (void)setDisabled:(BOOL)disabled {
@@ -61,13 +29,13 @@
     return;
   }
   _disabled = disabled;
-  [self updateAccessibilityLabel];
+  [self updateAccessibilityTraits];
 }
 
 #pragma mark - Private
 
-// Updates self.accessibilityLabel based on the current property values.
-- (void)updateAccessibilityLabel {
+// Updates self.accessibilityTraits based on the current property values.
+- (void)updateAccessibilityTraits {
   if (self.disabled) {
     self.accessibilityTraits =
         super.accessibilityTraits | UIAccessibilityTraitNotEnabled;
@@ -75,22 +43,6 @@
     self.accessibilityTraits =
         super.accessibilityTraits & ~UIAccessibilityTraitNotEnabled;
   }
-
-  // Resetting self.accessibilityLabel to nil will prompt self.title to be used
-  // as the default label.  This default value should be used if:
-  // - the cell is not for Reading List,
-  // - there are no unread articles in the reading list.
-  if (self.collectionShortcutType != NTPCollectionShortcutTypeReadingList ||
-      self.count <= 0) {
-    self.accessibilityLabel = nil;
-    return;
-  }
-
-  self.accessibilityLabel =
-      [NSString stringWithFormat:@"%@, %@", self.title,
-                                 AccessibilityLabelForReadingListCellWithCount(
-                                     self.count)];
-  DCHECK(self.accessibilityLabel.length);
 }
 
 @end
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_tile_view.h b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_tile_view.h
new file mode 100644
index 0000000..0b8caff
--- /dev/null
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_tile_view.h
@@ -0,0 +1,43 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_CONTENT_SUGGESTIONS_MOST_VISITED_ACTION_TILE_VIEW_H_
+#define IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_CONTENT_SUGGESTIONS_MOST_VISITED_ACTION_TILE_VIEW_H_
+
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_view.h"
+
+@class ContentSuggestionsMostVisitedActionItem;
+
+// A tile view displaying an action in content suggestions section. Accepts a
+// simple icon and optionally supports a badge, for example for reading list new
+// item count.
+@interface ContentSuggestionsMostVisitedActionTileView
+    : ContentSuggestionsTileView
+
+// Initializes and configures the view with `config`.
+- (instancetype)initWithConfiguration:
+    (ContentSuggestionsMostVisitedActionItem*)config;
+
+// Updates the configuration for this view to the new `config`.
+- (void)updateConfiguration:(ContentSuggestionsMostVisitedActionItem*)config;
+
+// View for action icon.
+@property(nonatomic, strong, readonly) UIImageView* iconView;
+
+// Container view for `countLabel`.
+@property(nonatomic, strong, readonly) UIView* countContainer;
+
+// Number shown in badge that is on the top trailing side of cell.
+@property(nonatomic, strong, readonly) UILabel* countLabel;
+
+// Configuration for this view.
+@property(nonatomic, strong, readonly)
+    ContentSuggestionsMostVisitedActionItem* config;
+
+// Tap gesture recognizer for this view.
+@property(nonatomic, strong) UITapGestureRecognizer* tapRecognizer;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_CONTENT_SUGGESTIONS_MOST_VISITED_ACTION_TILE_VIEW_H_
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_tile_view.mm b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_tile_view.mm
new file mode 100644
index 0000000..ab572ca
--- /dev/null
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_tile_view.mm
@@ -0,0 +1,197 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_tile_view.h"
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_cells_constants.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h"
+#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_color_palette.h"
+#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_image_background_trait.h"
+#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_trait.h"
+#import "ios/chrome/browser/shared/public/features/features.h"
+#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
+#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
+#import "ios/chrome/common/ui/util/constraints_ui_util.h"
+#import "ios/chrome/common/ui/util/dynamic_type_util.h"
+
+namespace {
+
+const CGFloat kCountWidth = 20;
+const CGFloat kCountBorderWidth = 24;
+
+}  // namespace
+
+@implementation ContentSuggestionsMostVisitedActionTileView
+@synthesize countLabel = _countLabel;
+
+- (instancetype)initWithFrame:(CGRect)frame {
+  self = [super initWithFrame:frame
+                     tileType:ContentSuggestionsTileType::kAction];
+  if (self) {
+    _iconView = [[UIImageView alloc] initWithFrame:self.bounds];
+    _iconView.translatesAutoresizingMaskIntoConstraints = NO;
+
+    [self.imageContainerView addSubview:_iconView];
+    AddSameConstraints(self.imageContainerView, _iconView);
+    [NSLayoutConstraint activateConstraints:@[
+      [_iconView.widthAnchor
+          constraintEqualToConstant:kMagicStackImageContainerWidth],
+      [_iconView.heightAnchor constraintEqualToAnchor:_iconView.widthAnchor],
+    ]];
+
+    [self registerViewForTraitChanges];
+    if (IsNTPBackgroundCustomizationEnabled()) {
+      [self applyBackgroundTheme];
+    } else {
+      self.imageBackgroundView.tintColor = [UIColor colorNamed:kBlueHaloColor];
+    }
+  }
+  return self;
+}
+
+- (instancetype)initWithConfiguration:
+    (ContentSuggestionsMostVisitedActionItem*)config {
+  self = [self initWithFrame:CGRectZero];
+  if (self) {
+    self.accessibilityCustomActions = nil;
+    self.isAccessibilityElement = YES;
+    _iconView.contentMode = UIViewContentModeCenter;
+
+    [self updateConfiguration:config];
+    [self registerViewForTraitChanges];
+  }
+  return self;
+}
+
+- (void)updateConfiguration:(ContentSuggestionsMostVisitedActionItem*)config {
+  _config = config;
+  self.titleLabel.text = config.title;
+  self.titleLabel.font = [self titleLabelFont];
+  self.accessibilityTraits =
+      UIAccessibilityTraitButton | config.accessibilityTraits;
+  self.accessibilityLabel = config.accessibilityLabel.length
+                                ? config.accessibilityLabel
+                                : config.title;
+  // The accessibilityUserInputLabel should just be the title, with nothing
+  // extra from the accessibilityLabel.
+  self.accessibilityUserInputLabels = @[ config.title ];
+  self.iconView.image = config.icon;
+  self.countContainer.hidden = config.count == 0;
+  if (config.count > 0) {
+    self.countLabel.text = [@(config.count) stringValue];
+  }
+  self.alpha = config.disabled ? 0.3 : 1.0;
+}
+
+- (UILabel*)countLabel {
+  if (!_countLabel) {
+    _countContainer = [[UIView alloc] init];
+    _countContainer.backgroundColor = [UIColor colorNamed:kBackgroundColor];
+    // Unfortunately, simply setting a CALayer borderWidth and borderColor
+    // on `_countContainer`, and setting a background color on `_countLabel`
+    // will result in the inner color bleeeding thru to the outside.
+    _countContainer.layer.cornerRadius = kCountBorderWidth / 2;
+    _countContainer.layer.masksToBounds = YES;
+
+    _countLabel = [[UILabel alloc] init];
+    _countLabel.layer.cornerRadius = kCountWidth / 2;
+    _countLabel.layer.masksToBounds = YES;
+    _countLabel.textColor = [UIColor colorNamed:kSolidButtonTextColor];
+    _countLabel.textAlignment = NSTextAlignmentCenter;
+
+    NewTabPageColorPalette* colorPalette =
+        [self.traitCollection objectForNewTabPageTrait];
+    // Only color the count label if there's no image background.
+    if (![self.traitCollection boolForNewTabPageImageBackgroundTrait] &&
+        colorPalette) {
+      _countLabel.backgroundColor = colorPalette.tintColor;
+    } else {
+      _countLabel.backgroundColor = [UIColor colorNamed:kBlueColor];
+    }
+
+    _countContainer.translatesAutoresizingMaskIntoConstraints = NO;
+    _countLabel.translatesAutoresizingMaskIntoConstraints = NO;
+
+    [self addSubview:self.countContainer];
+    [self.countContainer addSubview:self.countLabel];
+
+    [NSLayoutConstraint activateConstraints:@[
+      [_countContainer.widthAnchor constraintEqualToConstant:kCountBorderWidth],
+      [_countContainer.heightAnchor
+          constraintEqualToAnchor:_countContainer.widthAnchor],
+      [_countContainer.topAnchor constraintEqualToAnchor:self.topAnchor],
+      [_countContainer.centerXAnchor
+          constraintEqualToAnchor:self.imageContainerView.trailingAnchor],
+      [_countLabel.widthAnchor constraintEqualToConstant:kCountWidth],
+      [_countLabel.heightAnchor
+          constraintEqualToAnchor:_countLabel.widthAnchor],
+    ]];
+    const CGFloat kCaptionFontSize = 12.0;
+    const UIFontWeight kCaptionFontWeight = UIFontWeightRegular;
+    _countLabel.font = [UIFont systemFontOfSize:kCaptionFontSize
+                                         weight:kCaptionFontWeight];
+    AddSameCenterConstraints(_countLabel, _countContainer);
+  }
+  return _countLabel;
+}
+
+#pragma mark - Private
+
+- (UIFont*)titleLabelFont {
+  return PreferredFontForTextStyleWithMaxCategory(
+      UIFontTextStyleCaption2,
+      self.traitCollection.preferredContentSizeCategory,
+      UIContentSizeCategoryAccessibilityLarge);
+}
+
+// Registers a list of UITraits to observe and invokes the
+// `updateTitleLabelFontOnTraitChange` function whenever one of the observed
+// trait's values change.
+- (void)registerViewForTraitChanges API_AVAILABLE(ios(17.0)) {
+  NSArray<UITrait>* traits = TraitCollectionSetForTraits(
+      @[ UITraitPreferredContentSizeCategory.class ]);
+  [self registerForTraitChanges:traits
+                     withAction:@selector(updateTitleLabelFontOnTraitChange)];
+  if (IsNTPBackgroundCustomizationEnabled()) {
+    [self registerForTraitChanges:
+              @[ NewTabPageTrait.class, NewTabPageImageBackgroundTrait.class ]
+                       withAction:@selector(applyBackgroundTheme)];
+  }
+}
+
+// Update the `titleLabel` font when the device's content size changes.
+- (void)updateTitleLabelFontOnTraitChange {
+  self.titleLabel.font = [self titleLabelFont];
+}
+
+// Sets the background using the current background image state, color palette,
+// or defaults if none is set.
+- (void)applyBackgroundTheme {
+  BOOL hasImageBackground =
+      [self.traitCollection boolForNewTabPageImageBackgroundTrait];
+  NewTabPageColorPalette* colorPalette =
+      [self.traitCollection objectForNewTabPageTrait];
+
+  if (hasImageBackground) {
+    self.imageBackgroundView.tintColor = [UIColor colorNamed:kGrey100Color];
+    self.iconView.tintColor = [UIColor colorNamed:kTextPrimaryColor];
+    // Don't create count label if it's not already created.
+    _countLabel.backgroundColor = [UIColor colorNamed:kBlueColor];
+  } else if (colorPalette) {
+    self.imageBackgroundView.tintColor = colorPalette.tertiaryColor;
+    self.iconView.tintColor = colorPalette.tintColor;
+    // Don't create count label if it's not already created.
+    _countLabel.backgroundColor = colorPalette.tintColor;
+  } else {
+    self.imageBackgroundView.tintColor = [UIColor colorNamed:kBlueHaloColor];
+    self.iconView.tintColor = nil;
+    // Don't create count label if it's not already created.
+    _countLabel.backgroundColor = [UIColor colorNamed:kBlueColor];
+  }
+}
+
+@end
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_plus_button_item.h b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_plus_button_item.h
new file mode 100644
index 0000000..719fea9
--- /dev/null
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_plus_button_item.h
@@ -0,0 +1,17 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_CONTENT_SUGGESTIONS_PLUS_BUTTON_ITEM_H_
+#define IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_CONTENT_SUGGESTIONS_PLUS_BUTTON_ITEM_H_
+
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h"
+
+/// The item for the "add pinned site" button at the endo of the most visited
+/// tile.
+@interface ContentSuggestionsPlusButtonItem
+    : ContentSuggestionsMostVisitedActionItem <UIContentConfiguration>
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_CONTENT_SUGGESTIONS_PLUS_BUTTON_ITEM_H_
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_plus_button_item.mm b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_plus_button_item.mm
new file mode 100644
index 0000000..364bcb1
--- /dev/null
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_plus_button_item.mm
@@ -0,0 +1,78 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_plus_button_item.h"
+
+#import "base/apple/foundation_util.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_cells_constants.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_tile_view.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_constants.h"
+
+/// The cell content view of the add pinned site button. It is subclassed from
+/// the most-visited-action button so it shares the same color theme with the
+/// shortcuts tiles.
+@interface ContentSuggestionsPlusButtonTileView
+    : ContentSuggestionsMostVisitedActionTileView <UIContentView>
+
+@end
+
+@implementation ContentSuggestionsPlusButtonTileView
+
+- (instancetype)initWithConfiguration:
+    (ContentSuggestionsMostVisitedActionItem*)config {
+  self = [super initWithConfiguration:config];
+  if (self) {
+    self.imageBackgroundView.layer.cornerRadius =
+        kMagicStackImageContainerWidth / 2;
+    self.imageBackgroundView.clipsToBounds = YES;
+  }
+  return self;
+}
+
+- (id<UIContentConfiguration>)configuration {
+  return base::apple::ObjCCastStrict<ContentSuggestionsPlusButtonItem>(
+      self.config);
+}
+
+- (void)setConfiguration:(id<UIContentConfiguration>)configuration {
+  if ([configuration isKindOfClass:ContentSuggestionsPlusButtonItem.class]) {
+    ContentSuggestionsPlusButtonItem* item =
+        base::apple::ObjCCastStrict<ContentSuggestionsPlusButtonItem>(
+            configuration);
+    [self updateConfiguration:[item copy]];
+  }
+}
+
+@end
+
+@implementation ContentSuggestionsPlusButtonItem
+
+- (instancetype)init {
+  self = [super init];
+  if (self) {
+    self.title = TitleForMostVisitedTilePlusButton();
+    self.icon = SymbolForMostVisitedTilePlusButton();
+  }
+  return self;
+}
+
+#pragma mark - UIContentConfiguration
+
+- (id<UIContentView>)makeContentView {
+  return
+      [[ContentSuggestionsPlusButtonTileView alloc] initWithConfiguration:self];
+}
+
+- (instancetype)updatedConfigurationForState:(id<UIConfigurationState>)state {
+  /// Plus button in most visited tile looks the same across different states.
+  return self;
+}
+
+#pragma mark - NSCopying
+
+- (id)copyWithZone:(NSZone*)zone {
+  return [[ContentSuggestionsPlusButtonItem alloc] init];
+}
+
+@end
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.h b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.h
new file mode 100644
index 0000000..a01f7f5
--- /dev/null
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.h
@@ -0,0 +1,22 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_CONTENT_SUGGESTIONS_SHORTCUT_ITEM_H_
+#define IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_CONTENT_SUGGESTIONS_SHORTCUT_ITEM_H_
+
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h"
+
+// Item containing an action button in shortcuts tile in the magic stack.
+@interface ContentSuggestionsShortcutItem
+    : ContentSuggestionsMostVisitedActionItem
+
+- (nonnull instancetype)initWithCollectionShortcutType:
+    (NTPCollectionShortcutType)type;
+
+// The collection that this item acts as a shortcut for.
+@property(nonatomic, assign) NTPCollectionShortcutType collectionShortcutType;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_CONTENT_SUGGESTIONS_SHORTCUT_ITEM_H_
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.mm b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.mm
new file mode 100644
index 0000000..31ea7eb0
--- /dev/null
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.mm
@@ -0,0 +1,35 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.h"
+
+#import "base/check.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_constants.h"
+
+@implementation ContentSuggestionsShortcutItem
+
+- (instancetype)initWithCollectionShortcutType:(NTPCollectionShortcutType)type {
+  self = [super init];
+  if (self) {
+    _collectionShortcutType = type;
+    self.title = TitleForCollectionShortcutType(_collectionShortcutType);
+    self.icon = SymbolForCollectionShortcutType(_collectionShortcutType);
+  }
+  return self;
+}
+
+- (NSString*)accessibilityLabel {
+  if (_collectionShortcutType == NTPCollectionShortcutTypeReadingList &&
+      self.count > 0) {
+    NSString* accessibilityLabel = [NSString
+        stringWithFormat:@"%@, %@", self.title,
+                         AccessibilityLabelForReadingListCellWithCount(
+                             self.count)];
+    CHECK(accessibilityLabel.length);
+    return accessibilityLabel;
+  }
+  return [super accessibilityLabel];
+}
+
+@end
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.h b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.h
index 1808d73..adf7c1f 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.h
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.h
@@ -5,38 +5,15 @@
 #ifndef IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_CONTENT_SUGGESTIONS_SHORTCUT_TILE_VIEW_H_
 #define IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_CONTENT_SUGGESTIONS_SHORTCUT_TILE_VIEW_H_
 
-#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_view.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_tile_view.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_consumer.h"
 
-@class ContentSuggestionsMostVisitedActionItem;
+@class ContentSuggestionsShortcutItem;
 
 // A tile view displaying a collection shortcut. Accepts a simple icon and
 // optionally supports a badge, for example for reading list new item count.
 @interface ContentSuggestionsShortcutTileView
-    : ContentSuggestionsTileView <ShortcutsConsumer>
-
-// Initializes and configures the view with `config`.
-- (instancetype)initWithConfiguration:
-    (ContentSuggestionsMostVisitedActionItem*)config;
-
-// Updates the configuration for this view to the new `config`.
-- (void)updateConfiguration:(ContentSuggestionsMostVisitedActionItem*)config;
-
-// View for action icon.
-@property(nonatomic, strong, readonly) UIImageView* iconView;
-
-// Container view for `countLabel`.
-@property(nonatomic, strong, readonly) UIView* countContainer;
-
-// Number shown in badge that is on the top trailing side of cell.
-@property(nonatomic, strong, readonly) UILabel* countLabel;
-
-// Configuration for this view.
-@property(nonatomic, strong, readonly)
-    ContentSuggestionsMostVisitedActionItem* config;
-
-// Tap gesture recognizer for this view.
-@property(nonatomic, strong) UITapGestureRecognizer* tapRecognizer;
+    : ContentSuggestionsMostVisitedActionTileView <ShortcutsConsumer>
 
 @end
 
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.mm b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.mm
index 321492be..a6c8185 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.mm
@@ -4,202 +4,23 @@
 
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.h"
 
-#import <UIKit/UIKit.h>
-
-#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_cells_constants.h"
-#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h"
-#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_color_palette.h"
-#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_image_background_trait.h"
-#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_trait.h"
-#import "ios/chrome/browser/shared/public/features/features.h"
-#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
-#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
-#import "ios/chrome/common/ui/colors/semantic_color_names.h"
-#import "ios/chrome/common/ui/util/constraints_ui_util.h"
-#import "ios/chrome/common/ui/util/dynamic_type_util.h"
-
-namespace {
-
-const CGFloat kCountWidth = 20;
-const CGFloat kCountBorderWidth = 24;
-
-}  // namespace
+#import "base/apple/foundation_util.h"
+#import "base/check.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.h"
 
 @implementation ContentSuggestionsShortcutTileView
-@synthesize countLabel = _countLabel;
-
-- (instancetype)initWithFrame:(CGRect)frame {
-  self = [super initWithFrame:frame
-                     tileType:ContentSuggestionsTileType::kShortcuts];
-  if (self) {
-    _iconView = [[UIImageView alloc] initWithFrame:self.bounds];
-    _iconView.translatesAutoresizingMaskIntoConstraints = NO;
-
-    [self.imageContainerView addSubview:_iconView];
-    AddSameConstraints(self.imageContainerView, _iconView);
-    [NSLayoutConstraint activateConstraints:@[
-      [_iconView.widthAnchor
-          constraintEqualToConstant:kMagicStackImageContainerWidth],
-      [_iconView.heightAnchor constraintEqualToAnchor:_iconView.widthAnchor],
-    ]];
-
-    [self registerViewForTraitChanges];
-    if (IsNTPBackgroundCustomizationEnabled()) {
-      [self applyBackgroundTheme];
-    } else {
-      self.imageBackgroundView.tintColor = [UIColor colorNamed:kBlueHaloColor];
-    }
-  }
-  return self;
-}
 
 - (instancetype)initWithConfiguration:
     (ContentSuggestionsMostVisitedActionItem*)config {
-  self = [self initWithFrame:CGRectZero];
-  if (self) {
-    self.accessibilityCustomActions = nil;
-    self.isAccessibilityElement = YES;
-    _iconView.contentMode = UIViewContentModeCenter;
+  CHECK([config isKindOfClass:ContentSuggestionsShortcutItem.class]);
+  return [super initWithConfiguration:config];
+}
 
+- (void)shortcutsItemConfigDidChange:(ContentSuggestionsShortcutItem*)config {
+  ContentSuggestionsShortcutItem* currentConfig =
+      base::apple::ObjCCastStrict<ContentSuggestionsShortcutItem>(self.config);
+  if (config.collectionShortcutType == currentConfig.collectionShortcutType) {
     [self updateConfiguration:config];
-    [self registerViewForTraitChanges];
-  }
-  return self;
-}
-
-- (void)shortcutsItemConfigDidChange:
-    (ContentSuggestionsMostVisitedActionItem*)config {
-  if (config.index != _config.index) {
-    return;
-  }
-  [self updateConfiguration:config];
-}
-
-- (void)updateConfiguration:(ContentSuggestionsMostVisitedActionItem*)config {
-  _config = config;
-  self.titleLabel.text = config.title;
-  self.titleLabel.font = [self titleLabelFont];
-  self.accessibilityTraits =
-      UIAccessibilityTraitButton | config.accessibilityTraits;
-  self.accessibilityLabel = config.accessibilityLabel.length
-                                ? config.accessibilityLabel
-                                : config.title;
-  // The accessibilityUserInputLabel should just be the title, with nothing
-  // extra from the accessibilityLabel.
-  self.accessibilityUserInputLabels = @[ config.title ];
-  self.iconView.image =
-      SymbolForCollectionShortcutType(config.collectionShortcutType);
-  self.countContainer.hidden = config.count == 0;
-  if (config.count > 0) {
-    self.countLabel.text = [@(config.count) stringValue];
-  }
-  self.alpha = config.disabled ? 0.3 : 1.0;
-}
-
-- (UILabel*)countLabel {
-  if (!_countLabel) {
-    _countContainer = [[UIView alloc] init];
-    _countContainer.backgroundColor = [UIColor colorNamed:kBackgroundColor];
-    // Unfortunately, simply setting a CALayer borderWidth and borderColor
-    // on `_countContainer`, and setting a background color on `_countLabel`
-    // will result in the inner color bleeeding thru to the outside.
-    _countContainer.layer.cornerRadius = kCountBorderWidth / 2;
-    _countContainer.layer.masksToBounds = YES;
-
-    _countLabel = [[UILabel alloc] init];
-    _countLabel.layer.cornerRadius = kCountWidth / 2;
-    _countLabel.layer.masksToBounds = YES;
-    _countLabel.textColor = [UIColor colorNamed:kSolidButtonTextColor];
-    _countLabel.textAlignment = NSTextAlignmentCenter;
-
-    NewTabPageColorPalette* colorPalette =
-        [self.traitCollection objectForNewTabPageTrait];
-    // Only color the count label if there's no image background.
-    if (![self.traitCollection boolForNewTabPageImageBackgroundTrait] &&
-        colorPalette) {
-      _countLabel.backgroundColor = colorPalette.tintColor;
-    } else {
-      _countLabel.backgroundColor = [UIColor colorNamed:kBlueColor];
-    }
-
-    _countContainer.translatesAutoresizingMaskIntoConstraints = NO;
-    _countLabel.translatesAutoresizingMaskIntoConstraints = NO;
-
-    [self addSubview:self.countContainer];
-    [self.countContainer addSubview:self.countLabel];
-
-    [NSLayoutConstraint activateConstraints:@[
-      [_countContainer.widthAnchor constraintEqualToConstant:kCountBorderWidth],
-      [_countContainer.heightAnchor
-          constraintEqualToAnchor:_countContainer.widthAnchor],
-      [_countContainer.topAnchor constraintEqualToAnchor:self.topAnchor],
-      [_countContainer.centerXAnchor
-          constraintEqualToAnchor:self.imageContainerView.trailingAnchor],
-      [_countLabel.widthAnchor constraintEqualToConstant:kCountWidth],
-      [_countLabel.heightAnchor
-          constraintEqualToAnchor:_countLabel.widthAnchor],
-    ]];
-    const CGFloat kCaptionFontSize = 12.0;
-    const UIFontWeight kCaptionFontWeight = UIFontWeightRegular;
-    _countLabel.font = [UIFont systemFontOfSize:kCaptionFontSize
-                                         weight:kCaptionFontWeight];
-    AddSameCenterConstraints(_countLabel, _countContainer);
-  }
-  return _countLabel;
-}
-
-#pragma mark - Private
-
-- (UIFont*)titleLabelFont {
-  return PreferredFontForTextStyleWithMaxCategory(
-      UIFontTextStyleCaption2,
-      self.traitCollection.preferredContentSizeCategory,
-      UIContentSizeCategoryAccessibilityLarge);
-}
-
-// Registers a list of UITraits to observe and invokes the
-// `updateTitleLabelFontOnTraitChange` function whenever one of the observed
-// trait's values change.
-- (void)registerViewForTraitChanges API_AVAILABLE(ios(17.0)) {
-  NSArray<UITrait>* traits = TraitCollectionSetForTraits(
-      @[ UITraitPreferredContentSizeCategory.class ]);
-  [self registerForTraitChanges:traits
-                     withAction:@selector(updateTitleLabelFontOnTraitChange)];
-  if (IsNTPBackgroundCustomizationEnabled()) {
-    [self registerForTraitChanges:
-              @[ NewTabPageTrait.class, NewTabPageImageBackgroundTrait.class ]
-                       withAction:@selector(applyBackgroundTheme)];
-  }
-}
-
-// Update the `titleLabel` font when the device's content size changes.
-- (void)updateTitleLabelFontOnTraitChange {
-  self.titleLabel.font = [self titleLabelFont];
-}
-
-// Sets the background using the current background image state, color palette,
-// or defaults if none is set.
-- (void)applyBackgroundTheme {
-  BOOL hasImageBackground =
-      [self.traitCollection boolForNewTabPageImageBackgroundTrait];
-  NewTabPageColorPalette* colorPalette =
-      [self.traitCollection objectForNewTabPageTrait];
-
-  if (hasImageBackground) {
-    self.imageBackgroundView.tintColor = [UIColor colorNamed:kGrey100Color];
-    self.iconView.tintColor = [UIColor colorNamed:kTextPrimaryColor];
-    // Don't create count label if it's not already created.
-    _countLabel.backgroundColor = [UIColor colorNamed:kBlueColor];
-  } else if (colorPalette) {
-    self.imageBackgroundView.tintColor = colorPalette.tertiaryColor;
-    self.iconView.tintColor = colorPalette.tintColor;
-    // Don't create count label if it's not already created.
-    _countLabel.backgroundColor = colorPalette.tintColor;
-  } else {
-    self.imageBackgroundView.tintColor = [UIColor colorNamed:kBlueHaloColor];
-    self.iconView.tintColor = nil;
-    // Don't create count label if it's not already created.
-    _countLabel.backgroundColor = [UIColor colorNamed:kBlueColor];
   }
 }
 
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_constants.h b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_constants.h
index 7071162..a922d84c 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_constants.h
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_constants.h
@@ -7,19 +7,13 @@
 
 #import <UIKit/UIKit.h>
 
-// The minimum index value of the Bookmarks Shortcut content in the order behind
-// the four Most Visited tiles. NTPCollectionShortcutType is used as a proxy for
-// index value of the Shortcuts content.
-const int kShortcutMinimumIndex = 4;
-
 // Enum listing the collection shortcuts on NTP and similar surfaces.
 typedef NS_ENUM(NSInteger, NTPCollectionShortcutType) {
-  NTPCollectionShortcutTypeBookmark = kShortcutMinimumIndex,
+  NTPCollectionShortcutTypeBookmark = 0,
   NTPCollectionShortcutTypeReadingList,
   NTPCollectionShortcutTypeRecentTabs,
   NTPCollectionShortcutTypeHistory,
   NTPCollectionShortcutTypeWhatsNew,
-  NTPCollectionShortcutTypeCount
 };
 
 // Returns a localized title for a given collection shortcut type.
@@ -34,4 +28,12 @@
 // `count` = 0, for a reading list tile with no badge.
 NSString* AccessibilityLabelForReadingListCellWithCount(int count);
 
+// Returns the localized string for the button to add more pinned sites to the
+// most visied tile.
+NSString* TitleForMostVisitedTilePlusButton();
+
+// Returns the symbol image for the button to add more pinned sites to the most
+// visied tile.
+UIImage* SymbolForMostVisitedTilePlusButton();
+
 #endif  // IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_CONTENT_SUGGESTIONS_TILE_CONSTANTS_H_
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_constants.mm b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_constants.mm
index 8b762925..3575c8d 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_constants.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_constants.mm
@@ -30,8 +30,6 @@
       return l10n_util::GetNSString(IDS_IOS_CONTENT_SUGGESTIONS_HISTORY);
     case NTPCollectionShortcutTypeWhatsNew:
       return l10n_util::GetNSString(IDS_IOS_CONTENT_SUGGESTIONS_WHATS_NEW);
-    case NTPCollectionShortcutTypeCount:
-      NOTREACHED();
   }
 }
 
@@ -52,8 +50,6 @@
     case NTPCollectionShortcutTypeWhatsNew:
       return DefaultSymbolTemplateWithPointSize(
           kCheckmarkSealSymbol, kSymbolContentSuggestionsPointSize);
-    case NTPCollectionShortcutTypeCount:
-      NOTREACHED();
   }
 }
 
@@ -70,3 +66,12 @@
     return l10n_util::GetNSString(messageID);
   }
 }
+
+NSString* TitleForMostVisitedTilePlusButton() {
+  return l10n_util::GetNSString(IDS_IOS_CONTENT_SUGGESTIONS_ADD_PINNED_SITE);
+}
+
+UIImage* SymbolForMostVisitedTilePlusButton() {
+  return DefaultSymbolTemplateWithPointSize(kPlusSymbol,
+                                            kSymbolContentSuggestionsPointSize);
+}
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_view.h b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_view.h
index 4ac6f2d..837beac1 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_view.h
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_view.h
@@ -9,7 +9,7 @@
 
 enum class ContentSuggestionsTileType {
   kMostVisited,
-  kShortcuts,
+  kAction,
 };
 
 // A generic Content Suggestions tile view. Provides a title label and an image
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_view.mm b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_view.mm
index fdd23177..f82101a 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_view.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_view.mm
@@ -56,7 +56,7 @@
     }
 
     // Use original rounded-square background image for Shorcuts
-    if (type == ContentSuggestionsTileType::kShortcuts) {
+    if (type == ContentSuggestionsTileType::kAction) {
       [self addSubview:_titleLabel];
 
       // The squircle background view.
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/most_visited_tiles_collection_view.mm b/ios/chrome/browser/content_suggestions/ui_bundled/cells/most_visited_tiles_collection_view.mm
index 543e61a..e89d5df3 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/most_visited_tiles_collection_view.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/most_visited_tiles_collection_view.mm
@@ -7,6 +7,7 @@
 #import "base/check.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_constants.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_item.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_plus_button_item.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_layout_util.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/most_visited_tiles_config.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_constants.h"
@@ -179,13 +180,7 @@
           @"%@%li", kContentSuggestionsMostVisitedAccessibilityIdentifierPrefix,
           identifier.longValue];
   if (identifier.intValue == kPlusButtonIdentifier) {
-    /// TODO(crbug.com/462459633): The following code is used for place-holding
-    /// purpose. Use the correct plus button UI.
-    UILabel* plusButton =
-        [[UILabel alloc] initWithFrame:CGRectMake(0, 0, kIconSize, kIconSize)];
-    plusButton.text = @"+";
-    plusButton.font = [UIFont systemFontOfSize:70];
-    [cell.contentView addSubview:plusButton];
+    cell.contentConfiguration = [[ContentSuggestionsPlusButtonItem alloc] init];
   } else {
     [self loadFaviconIfNeeded:identifier];
     cell.contentConfiguration = _items[identifier.unsignedIntValue];
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/most_visited_tiles_mediator.mm b/ios/chrome/browser/content_suggestions/ui_bundled/cells/most_visited_tiles_mediator.mm
index f2b72e4..06f4f6d 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/most_visited_tiles_mediator.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/most_visited_tiles_mediator.mm
@@ -156,7 +156,6 @@
     item.incognitoAvailable = _incognitoAvailable;
     item.index = index;
     item.menuElementsProvider = self;
-    DCHECK(index < kShortcutMinimumIndex);
     index++;
     [_freshMostVisitedItems addObject:item];
   }
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_config.h b/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_config.h
index b62ac9ad..869161d 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_config.h
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_config.h
@@ -9,7 +9,7 @@
 
 @protocol ShortcutsCommands;
 @protocol ShortcutsConsumerSource;
-@class ContentSuggestionsMostVisitedActionItem;
+@class ContentSuggestionsShortcutItem;
 
 #import "ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_module.h"
 
@@ -18,7 +18,7 @@
 
 // List of Shortcuts to show in module.
 @property(nonatomic, strong)
-    NSArray<ContentSuggestionsMostVisitedActionItem*>* shortcutItems;
+    NSArray<ContentSuggestionsShortcutItem*>* shortcutItems;
 
 // Shortcuts model.
 @property(nonatomic, weak) id<ShortcutsConsumerSource> consumerSource;
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_consumer.h b/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_consumer.h
index e139195..6ce17ef 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_consumer.h
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_consumer.h
@@ -5,14 +5,13 @@
 #ifndef IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_SHORTCUTS_CONSUMER_H_
 #define IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_CELLS_SHORTCUTS_CONSUMER_H_
 
-@class ContentSuggestionsMostVisitedActionItem;
+@class ContentSuggestionsShortcutItem;
 
 // Interface for listening to events occurring in ShortcutsMediator.
 @protocol ShortcutsConsumer
 @optional
 // Indicates that the `config` has been updated.
-- (void)shortcutsItemConfigDidChange:
-    (ContentSuggestionsMostVisitedActionItem*)config;
+- (void)shortcutsItemConfigDidChange:(ContentSuggestionsShortcutItem*)config;
 
 @end
 
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_mediator.mm b/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_mediator.mm
index 32422f5..dada2066 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_mediator.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_mediator.mm
@@ -11,7 +11,7 @@
 #import "components/reading_list/core/reading_list_model.h"
 #import "components/reading_list/ios/reading_list_model_bridge_observer.h"
 #import "components/signin/public/identity_manager/identity_manager.h"
-#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_commands.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_config.h"
@@ -41,7 +41,7 @@
   std::unique_ptr<ReadingListModelBridge> _readingListModelBridge;
   // Item for the reading list action item.  Reference is used to update the
   // reading list count.
-  ContentSuggestionsMostVisitedActionItem* _readingListItem;
+  ContentSuggestionsShortcutItem* _readingListItem;
   // Indicates if reading list model is loaded. Readlist cannot be triggered
   // until it is.
   BOOL _readingListModelIsLoaded;
@@ -80,21 +80,21 @@
   _identityManager = nil;
 }
 
-- (NSArray<ContentSuggestionsMostVisitedActionItem*>*)shortcutItems {
-  _readingListItem = [[ContentSuggestionsMostVisitedActionItem alloc]
+- (NSArray<ContentSuggestionsShortcutItem*>*)shortcutItems {
+  _readingListItem = [[ContentSuggestionsShortcutItem alloc]
       initWithCollectionShortcutType:NTPCollectionShortcutTypeReadingList];
   _readingListItem.count = _readingListUnreadCount;
   _readingListItem.disabled = !_readingListModelIsLoaded;
-  NSArray<ContentSuggestionsMostVisitedActionItem*>* shortcuts = @[
+  NSArray<ContentSuggestionsShortcutItem*>* shortcuts = @[
     [self shouldShowWhatsNewActionItem]
-        ? [[ContentSuggestionsMostVisitedActionItem alloc]
+        ? [[ContentSuggestionsShortcutItem alloc]
               initWithCollectionShortcutType:NTPCollectionShortcutTypeWhatsNew]
-        : [[ContentSuggestionsMostVisitedActionItem alloc]
+        : [[ContentSuggestionsShortcutItem alloc]
               initWithCollectionShortcutType:NTPCollectionShortcutTypeBookmark],
     _readingListItem,
-    [[ContentSuggestionsMostVisitedActionItem alloc]
+    [[ContentSuggestionsShortcutItem alloc]
         initWithCollectionShortcutType:NTPCollectionShortcutTypeRecentTabs],
-    [[ContentSuggestionsMostVisitedActionItem alloc]
+    [[ContentSuggestionsShortcutItem alloc]
         initWithCollectionShortcutType:NTPCollectionShortcutTypeHistory]
   ];
   return shortcuts;
@@ -118,8 +118,8 @@
   ContentSuggestionsShortcutTileView* shortcutView =
       static_cast<ContentSuggestionsShortcutTileView*>(sender.view);
 
-  ContentSuggestionsMostVisitedActionItem* shortcutsItem =
-      base::apple::ObjCCastStrict<ContentSuggestionsMostVisitedActionItem>(
+  ContentSuggestionsShortcutItem* shortcutsItem =
+      base::apple::ObjCCastStrict<ContentSuggestionsShortcutItem>(
           shortcutView.config);
   if (shortcutsItem.disabled) {
     return;
@@ -145,8 +145,6 @@
     case NTPCollectionShortcutTypeWhatsNew:
       [self.dispatcher showWhatsNew];
       break;
-    case NTPCollectionShortcutTypeCount:
-      NOTREACHED();
   }
   return;
 }
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_mediator_unittest.mm b/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_mediator_unittest.mm
index b32f29c..5159ca8 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_mediator_unittest.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/shortcuts_mediator_unittest.mm
@@ -9,7 +9,7 @@
 #import "components/reading_list/core/fake_reading_list_model_storage.h"
 #import "components/reading_list/core/reading_list_model_impl.h"
 #import "components/signin/public/identity_manager/identity_manager.h"
-#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_constants.h"
 #import "ios/chrome/browser/feature_engagement/model/tracker_factory.h"
@@ -85,8 +85,8 @@
       logMagicStackEngagementForType:ContentSuggestionsModuleType::kShortcuts]);
 
   // Action.
-  ContentSuggestionsMostVisitedActionItem* readingList =
-      [[ContentSuggestionsMostVisitedActionItem alloc]
+  ContentSuggestionsShortcutItem* readingList =
+      [[ContentSuggestionsShortcutItem alloc]
           initWithCollectionShortcutType:NTPCollectionShortcutTypeReadingList];
   ContentSuggestionsShortcutTileView* shortcutView =
       [[ContentSuggestionsShortcutTileView alloc]
@@ -109,8 +109,8 @@
       logMagicStackEngagementForType:ContentSuggestionsModuleType::kShortcuts]);
 
   // Action.
-  ContentSuggestionsMostVisitedActionItem* whatsNew =
-      [[ContentSuggestionsMostVisitedActionItem alloc]
+  ContentSuggestionsShortcutItem* whatsNew =
+      [[ContentSuggestionsShortcutItem alloc]
           initWithCollectionShortcutType:NTPCollectionShortcutTypeWhatsNew];
   ContentSuggestionsShortcutTileView* shortcutView =
       [[ContentSuggestionsShortcutTileView alloc]
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_metrics_recorder.mm b/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_metrics_recorder.mm
index 0ac51a1..e4e2f4f 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_metrics_recorder.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_metrics_recorder.mm
@@ -141,8 +141,6 @@
     case NTPCollectionShortcutTypeWhatsNew:
       base::RecordAction(base::UserMetricsAction(kShowWhatsNewAction));
       break;
-    case NTPCollectionShortcutTypeCount:
-      NOTREACHED();
   }
 }
 
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_module_contents_factory.mm b/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_module_contents_factory.mm
index f838bc74..8a5d6f0 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_module_contents_factory.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_module_contents_factory.mm
@@ -7,6 +7,7 @@
 #import "base/notreached.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/ui/app_bundle_promo_config.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/ui/app_bundle_promo_view.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_layout_util.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/most_visited_tiles_collection_view.h"
@@ -133,8 +134,7 @@
 - (UIView*)shortcutsStackViewForConfig:(ShortcutsConfig*)shortcutsConfig
                            tileSpacing:(CGFloat)spacing {
   NSMutableArray* shortcutsViews = [NSMutableArray array];
-  for (ContentSuggestionsMostVisitedActionItem* item in shortcutsConfig
-           .shortcutItems) {
+  for (ContentSuggestionsShortcutItem* item in shortcutsConfig.shortcutItems) {
     ContentSuggestionsShortcutTileView* view =
         [[ContentSuggestionsShortcutTileView alloc] initWithConfiguration:item];
     [shortcutsConfig.consumerSource addConsumer:view];
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_ranking_model_unittest.mm b/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_ranking_model_unittest.mm
index 5b8082f..41a05b9 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_ranking_model_unittest.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_ranking_model_unittest.mm
@@ -33,8 +33,8 @@
 #import "components/sync_preferences/testing_pref_service_syncable.h"
 #import "components/ukm/test_ukm_recorder.h"
 #import "ios/chrome/browser/commerce/model/shopping_service_factory.h"
-#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_item.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_tile_view.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/most_visited_tiles_config.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/most_visited_tiles_mediator.h"
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/safety_check/ui/BUILD.gn b/ios/chrome/browser/content_suggestions/ui_bundled/safety_check/ui/BUILD.gn
index 13bed53..cbb54b0 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/safety_check/ui/BUILD.gn
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/safety_check/ui/BUILD.gn
@@ -6,8 +6,6 @@
   sources = [
     "safety_check_audience.h",
     "safety_check_consumer_source.h",
-    "safety_check_item_icon.h",
-    "safety_check_item_icon.mm",
     "safety_check_magic_stack_consumer.h",
     "safety_check_state.h",
     "safety_check_state.mm",
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/safety_check/ui/safety_check_item_icon.h b/ios/chrome/browser/content_suggestions/ui_bundled/safety_check/ui/safety_check_item_icon.h
deleted file mode 100644
index 20a979d..0000000
--- a/ios/chrome/browser/content_suggestions/ui_bundled/safety_check/ui/safety_check_item_icon.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_SAFETY_CHECK_UI_SAFETY_CHECK_ITEM_ICON_H_
-#define IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_SAFETY_CHECK_UI_SAFETY_CHECK_ITEM_ICON_H_
-
-#import <UIKit/UIKit.h>
-
-// A view which contains an icon for a Safety Check item.
-@interface SafetyCheckItemIcon : UIView
-
-// Instantiates a `SafetyCheckItemIcon` given a `defaultSymbolName`.
-//
-// `compactLayout` determines if the icon should be shown in a smaller, compact
-// size.
-//
-// `inSquare` determines if the icon should be shown with a square enclosure
-// surrounding it.
-- (instancetype)initWithDefaultSymbol:(NSString*)defaultSymbolName
-                        compactLayout:(BOOL)compactLayout
-                             inSquare:(BOOL)inSquare;
-
-// Instantiates a `SafetyCheckItemIcon` given a `customSymbolName`.
-//
-// `compactLayout` determines if the icon should be shown in a smaller, compact
-// size.
-//
-// `inSquare` determines if the icon should be shown with a square enclosure
-// surrounding it.
-- (instancetype)initWithCustomSymbol:(NSString*)customSymbolName
-                       compactLayout:(BOOL)compactLayout
-                            inSquare:(BOOL)inSquare;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_CONTENT_SUGGESTIONS_UI_BUNDLED_SAFETY_CHECK_UI_SAFETY_CHECK_ITEM_ICON_H_
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/safety_check/ui/safety_check_item_icon.mm b/ios/chrome/browser/content_suggestions/ui_bundled/safety_check/ui/safety_check_item_icon.mm
deleted file mode 100644
index b24b8b0..0000000
--- a/ios/chrome/browser/content_suggestions/ui_bundled/safety_check/ui/safety_check_item_icon.mm
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/content_suggestions/ui_bundled/safety_check/ui/safety_check_item_icon.h"
-
-#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
-#import "ios/chrome/common/ui/colors/semantic_color_names.h"
-#import "ios/chrome/common/ui/util/constraints_ui_util.h"
-
-namespace {
-
-// Constants related to icon sizing.
-constexpr CGFloat kIconSize = 22;
-constexpr CGFloat kIconContainerSize = 30;
-constexpr CGFloat kIconSquareContainerRadius = 7;
-// NOTE: The Safety Check (Magic Stack) module uses a slightly larger version of
-// the SF Symbols Password icon in its design.
-constexpr CGFloat kPasswordIconSize = 26;
-
-// Returns an icon-specific width given `symbol`.
-CGFloat IconWidthForSymbolName(NSString* symbol) {
-  if (symbol == kPasswordSymbol) {
-    return kPasswordIconSize;
-  }
-
-  return kIconSize;
-}
-
-// Returns a UIImageView for the given SF Symbol with color(s) `color_palette`,
-// using `default_symbol`.
-UIImageView* IconForSymbol(NSString* symbol,
-                           BOOL default_symbol,
-                           NSArray<UIColor*>* color_palette = nil) {
-  UIImageSymbolConfiguration* config = [UIImageSymbolConfiguration
-      configurationWithWeight:UIImageSymbolWeightMedium];
-
-  if (color_palette) {
-    UIImageSymbolConfiguration* colorConfig = [UIImageSymbolConfiguration
-        configurationWithPaletteColors:color_palette];
-
-    config = [config configurationByApplyingConfiguration:colorConfig];
-  }
-
-  UIImage* image = default_symbol
-                       ? DefaultSymbolWithConfiguration(symbol, config)
-                       : CustomSymbolWithConfiguration(symbol, config);
-
-  UIImageView* icon = [[UIImageView alloc] initWithImage:image];
-
-  icon.translatesAutoresizingMaskIntoConstraints = NO;
-
-  CGFloat icon_width = IconWidthForSymbolName(symbol);
-
-  [NSLayoutConstraint activateConstraints:@[
-    [icon.widthAnchor constraintEqualToConstant:icon_width],
-    [icon.heightAnchor constraintEqualToAnchor:icon.widthAnchor],
-  ]];
-
-  return icon;
-}
-
-// Returns a UIView for the given `icon` wrapped in a container with
-// `containerColor`.
-UIView* IconInSquareContainer(UIImageView* icon, NSString* containerColor) {
-  UIView* square_view = [[UIView alloc] init];
-
-  square_view.translatesAutoresizingMaskIntoConstraints = NO;
-  square_view.layer.cornerRadius = kIconSquareContainerRadius;
-  square_view.backgroundColor = [UIColor colorNamed:containerColor];
-
-  icon.contentMode = UIViewContentModeScaleAspectFit;
-
-  [square_view addSubview:icon];
-
-  AddSameCenterConstraints(icon, square_view);
-
-  [NSLayoutConstraint activateConstraints:@[
-    [square_view.widthAnchor constraintEqualToConstant:kIconContainerSize],
-    [square_view.heightAnchor constraintEqualToAnchor:square_view.widthAnchor],
-  ]];
-
-  return square_view;
-}
-
-}  // namespace
-
-@implementation SafetyCheckItemIcon {
-  // The symbol name for the icon.
-  NSString* _symbol;
-  // YES if `_symbol` is a default symbol name. (NO if `_symbol` is a custom
-  // symbol name.)
-  BOOL _defaultSymbol;
-  // YES if this icon should configure itself in a smaller, compact
-  // size.
-  BOOL _compactLayout;
-  // YES if this icon should place itself within a square enclosure.
-  BOOL _inSquare;
-  // The view containing the icon.
-  UIView* _icon;
-}
-
-- (instancetype)initWithDefaultSymbol:(NSString*)defaultSymbolName
-                        compactLayout:(BOOL)compactLayout
-                             inSquare:(BOOL)inSquare {
-  if ((self = [super init])) {
-    _symbol = defaultSymbolName;
-    _defaultSymbol = YES;
-    _compactLayout = compactLayout;
-    _inSquare = inSquare;
-  }
-
-  return self;
-}
-
-- (instancetype)initWithCustomSymbol:(NSString*)customSymbolName
-                       compactLayout:(BOOL)compactLayout
-                            inSquare:(BOOL)inSquare {
-  if ((self = [super init])) {
-    _symbol = customSymbolName;
-    _defaultSymbol = NO;
-    _compactLayout = compactLayout;
-    _inSquare = inSquare;
-  }
-
-  return self;
-}
-
-#pragma mark - UIView
-
-- (void)willMoveToSuperview:(UIView*)newSuperview {
-  [super willMoveToSuperview:newSuperview];
-
-  [self createSubviews];
-}
-
-#pragma mark - Private
-
-// Creates all views for the icon of a particular check row in the Safety Check
-// (Magic Stack) module.
-- (void)createSubviews {
-  // Return if the subviews have already been created and added.
-  if (!(self.subviews.count == 0)) {
-    return;
-  }
-
-  self.tintAdjustmentMode = UIViewTintAdjustmentModeNormal;
-
-  _icon = [self createIcon];
-
-  [self addSubview:_icon];
-
-  AddSameConstraints(self, _icon);
-}
-
-// Creates the type-specific icon.
-- (UIView*)createIcon {
-  // Compact, in-square icons are displayed in light blue.
-  if (_inSquare && _compactLayout) {
-    UIImageView* icon = IconForSymbol(_symbol, _defaultSymbol,
-                                      @[ [UIColor colorNamed:kBlue500Color] ]);
-
-    return IconInSquareContainer(icon, kBlueHaloColor);
-  }
-
-  // Non-compact, in-square icons are displayed in white.
-  if (_inSquare) {
-    UIImageView* icon =
-        IconForSymbol(_symbol, _defaultSymbol, @[ [UIColor whiteColor] ]);
-
-    return IconInSquareContainer(icon, kBlue500Color);
-  }
-
-  // By default, display icons in gray, with a square container.
-  return IconForSymbol(_symbol, _defaultSymbol,
-                       @[ [UIColor colorNamed:kGrey500Color] ]);
-}
-
-@end
diff --git a/ios/chrome/browser/credential_provider/model/credential_provider_service_unittest.mm b/ios/chrome/browser/credential_provider/model/credential_provider_service_unittest.mm
index 647dcb6..b5e0cf2 100644
--- a/ios/chrome/browser/credential_provider/model/credential_provider_service_unittest.mm
+++ b/ios/chrome/browser/credential_provider/model/credential_provider_service_unittest.mm
@@ -313,11 +313,8 @@
   // Set managed account as the primary one.
   CoreAccountInfo core_account =
       identity_test_environment_.MakeAccountAvailable("foo@gmail.com");
-  AccountInfo account;
-  account.account_id = core_account.account_id;
-  account.gaia = core_account.gaia;
-  account.email = core_account.email;
-  account.hosted_domain = "managed.com";
+  AccountInfo account =
+      AccountInfo::Builder(core_account).SetHostedDomain("managed.com").Build();
   ASSERT_EQ(account.IsManaged(), signin::Tribool::kTrue);
   identity_test_environment_.UpdateAccountInfoForAccount(account);
   identity_test_environment_.SetPrimaryAccount("foo@gmail.com",
diff --git a/ios/chrome/browser/enterprise/data_controls/test/data_controls_egtest.mm b/ios/chrome/browser/enterprise/data_controls/test/data_controls_egtest.mm
index 2461a246..41512fe 100644
--- a/ios/chrome/browser/enterprise/data_controls/test/data_controls_egtest.mm
+++ b/ios/chrome/browser/enterprise/data_controls/test/data_controls_egtest.mm
@@ -23,7 +23,6 @@
 namespace {
 
 NSString* const kCopyConditionName = @"Link copied condition";
-
 NSString* const kLoadReaderModeFailedMessage =
     @"Reader mode content could not be loaded";
 NSString* const kCopyLinkFailedMessage = @"Copying link failed";
@@ -32,21 +31,37 @@
 
 // Path to a page compatible with reader mode.
 const char kArticlePath[] = "/article.html";
-
 // URL to a page with a static message.
 const char kDestinationPageUrl[] = "/destination";
+// Path to a page containing the chromium logo and the text `kLogoPageText`.
+const char kLogoPagePath[] = "/chromium_logo_page.html";
+// The DOM element ID of the chromium image on the logo page.
+const char kLogoPageChromiumImageId[] = "chromium_image";
+// The text of the message on the logo page.
+const char kLogoPageText[] = "Page with some text and the chromium logo image.";
 
 // Returns an ElementSelector for long pressing the first link in the page.
 ElementSelector* ElementSelectorToLongPressLink() {
   return [ElementSelector selectorWithCSSSelector:"a"];
 }
 
+// Returns an ElementSelector for the chromium image on the logo page.
+ElementSelector* LogoPageChromiumImageIdSelector() {
+  return [ElementSelector selectorWithElementID:kLogoPageChromiumImageId];
+}
+
 // Matcher for the copy link button in the context menu.
 id<GREYMatcher> CopyLinkButton() {
   return ContextMenuItemWithAccessibilityLabelId(
       IDS_IOS_COPY_LINK_ACTION_TITLE);
 }
 
+// Matcher for the open link in group button in the context menu.
+id<GREYMatcher> CopyImageButton() {
+  return ContextMenuItemWithAccessibilityLabelId(
+      IDS_IOS_CONTENT_CONTEXT_COPYIMAGE);
+}
+
 // Taps on `context_menu_item_button` context menu item.
 void TapOnContextMenuButton(id<GREYMatcher> context_menu_item_button) {
   [ChromeEarlGrey waitForUIElementToAppearWithMatcher:context_menu_item_button];
@@ -87,9 +102,82 @@
 
 #pragma mark - Tests
 
-// Tests that copy is blocked when a "BLOCK" rule matches the page URL.
-- (void)DISABLED_testCopyBlocked {
-  // TODO(crbug.com/457472925): This is a placeholder, update the tests.
+// Tests that copying an image via context menu is blocked when a "BLOCK" is set
+// in DataControls policy.
+- (void)testCopyBlocked {
+  [DataControlsAppInterface setBlockCopyRule];
+
+  [ChromeEarlGrey clearPasteboard];
+  [ChromeEarlGrey loadURL:self.testServer->GetURL(kLogoPagePath)];
+  [ChromeEarlGrey waitForWebStateContainingText:kLogoPageText];
+
+  [ChromeEarlGreyUI
+      longPressElementOnWebView:LogoPageChromiumImageIdSelector()];
+  TapOnContextMenuButton(CopyImageButton());
+
+  // Check that the snackbar is shown.
+  id<GREYMatcher> snackbarMessage = grey_text(
+      l10n_util::GetNSString(IDS_POLICY_ACTION_BLOCKED_BY_ORGANIZATION));
+  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:snackbarMessage];
+
+  // Check that the image was not copied.
+  GREYAssertFalse([ChromeEarlGrey pasteboardHasImages],
+                  @"Image should not have been copied");
+  [DataControlsAppInterface clearDataControlRules];
+}
+
+// Tests that copying an image via context menu is allowed after the user
+// proceeds through the warning triggered by DataControlRules policy.
+- (void)testCopyWarnProceed {
+  [DataControlsAppInterface setWarnCopyRule];
+
+  [ChromeEarlGrey clearPasteboard];
+  [ChromeEarlGrey loadURL:self.testServer->GetURL(kLogoPagePath)];
+  [ChromeEarlGrey waitForWebStateContainingText:kLogoPageText];
+
+  [ChromeEarlGreyUI
+      longPressElementOnWebView:LogoPageChromiumImageIdSelector()];
+  TapOnContextMenuButton(CopyImageButton());
+
+  // Tap the "Copy anyways" button on the warning dialog.
+  [[EarlGrey selectElementWithMatcher:
+                 chrome_test_util::AlertItemWithAccessibilityLabelId(
+                     IDS_DATA_CONTROLS_COPY_WARN_CONTINUE_BUTTON)]
+      performAction:grey_tap()];
+
+  // Check that the image was copied.
+  GREYCondition* copyCondition =
+      [GREYCondition conditionWithName:@"Image copied condition"
+                                 block:^BOOL {
+                                   return [ChromeEarlGrey pasteboardHasImages];
+                                 }];
+  GREYAssertTrue([copyCondition waitWithTimeout:5], @"Copying image failed");
+  [ChromeEarlGrey clearPasteboard];
+  [DataControlsAppInterface clearDataControlRules];
+}
+
+// Tests that copying an image via context menu is cancelled when the user
+// cancels on the warning triggered by DataControlRules policy.
+- (void)testCopyWarnCancel {
+  [DataControlsAppInterface setWarnCopyRule];
+
+  [ChromeEarlGrey clearPasteboard];
+  [ChromeEarlGrey loadURL:self.testServer->GetURL(kLogoPagePath)];
+  [ChromeEarlGrey waitForWebStateContainingText:kLogoPageText];
+
+  [ChromeEarlGreyUI
+      longPressElementOnWebView:LogoPageChromiumImageIdSelector()];
+  TapOnContextMenuButton(CopyImageButton());
+
+  // Tap the "cancel" button on the warning dialog.
+  [[EarlGrey selectElementWithMatcher:
+                 chrome_test_util::AlertItemWithAccessibilityLabelId(
+                     IDS_DATA_CONTROLS_COPY_WARN_CANCEL_BUTTON)]
+      performAction:grey_tap()];
+  // Check that the image was not copied.
+  GREYAssertFalse([ChromeEarlGrey pasteboardHasImages],
+                  @"Image should not have been copied");
+  [DataControlsAppInterface clearDataControlRules];
 }
 
 // Tests that copying a link is blocked in reader mode when the DataControlsRule
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 95f0ec0..a1faa1dd 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -2851,6 +2851,10 @@
      flag_descriptions::kAIMEligibilityServiceStartWithProfileDescription,
      flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kAIMEligibilityServiceStartWithProfile)},
+    {"lens-omnient-shader-v2-enabled",
+     flag_descriptions::kLensOmnientShaderV2EnabledName,
+     flag_descriptions::kLensOmnientShaderV2EnabledDescription,
+     flags_ui::kOsIos, FEATURE_VALUE_TYPE(kLensOmnientShaderV2Enabled)},
     {"aimntp-entrypoint-tablet", flag_descriptions::kAIMNTPEntrypointTabletName,
      flag_descriptions::kAIMNTPEntrypointTabletDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kAIMNTPEntrypointTablet)},
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index 64ee67c..1f97ab9 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -944,6 +944,10 @@
 const char kLensLoadAIMInLensResultPageDescription[] =
     "Opens in Lens result page rather than a new tab.";
 
+const char kLensOmnientShaderV2EnabledName[] = "Enable Lens Omnient Shader V2";
+const char kLensOmnientShaderV2EnabledDescription[] =
+    "When enabled, Lens Omnient will use the new Shader V2";
+
 const char kLensOverlayCustomBottomSheetName[] =
     "Use a custom bottom sheet presentation for Lens Overlay";
 const char kLensOverlayCustomBottomSheetDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index cf99b5b..f148657 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -566,6 +566,9 @@
 extern const char kLensLoadAIMInLensResultPageName[];
 extern const char kLensLoadAIMInLensResultPageDescription[];
 
+extern const char kLensOmnientShaderV2EnabledName[];
+extern const char kLensOmnientShaderV2EnabledDescription[];
+
 extern const char kLensOverlayCustomBottomSheetName[];
 extern const char kLensOverlayCustomBottomSheetDescription[];
 
diff --git a/ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.mm b/ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.mm
index 2cce7a2a..9e425b6d 100644
--- a/ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.mm
+++ b/ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.mm
@@ -37,15 +37,12 @@
 #import "ios/chrome/browser/shared/public/features/features.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"
 #import "ios/chrome/common/ui/util/constraints_ui_util.h"
 #import "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace {
 
-// Enables the liquid glass effect for the home customization menu background.
-BASE_FEATURE(kHomeCustomizationLiquidGlassBackground,
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // The height of the menu's initial detent, which roughly represents a header
 // and 3 cells.
 const CGFloat kInitialDetentHeight = 350;
@@ -361,9 +358,10 @@
       [[UINavigationController alloc] initWithRootViewController:menuPage];
 
   if (@available(iOS 26, *)) {
-    if (base::FeatureList::IsEnabled(kHomeCustomizationLiquidGlassBackground)) {
-      menuPage.view.backgroundColor = [UIColor clearColor];
-    }
+    menuPage.view.backgroundColor = [UIColor clearColor];
+  } else {
+    menuPage.view.backgroundColor =
+        [UIColor colorNamed:kGroupedPrimaryBackgroundColor];
   }
 
   navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
@@ -454,6 +452,7 @@
   _backgroundPickerActionSheetCoordinator.presentationDelegate = self;
   _backgroundPickerActionSheetCoordinator.searchEngineLogoMediatorProvider =
       self;
+
   [_backgroundPickerActionSheetCoordinator start];
   // Disable customization interactions while the background picker views are
   // open so the user can't choose a new background from the main menu while in
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_background_cell.mm b/ios/chrome/browser/home_customization/ui/home_customization_background_cell.mm
index b99a1c8d..77379ca1 100644
--- a/ios/chrome/browser/home_customization/ui/home_customization_background_cell.mm
+++ b/ios/chrome/browser/home_customization/ui/home_customization_background_cell.mm
@@ -134,6 +134,7 @@
     self.innerContentView.layer.cornerRadius = kContentViewCornerRadius;
     self.innerContentView.layer.masksToBounds = YES;
     self.innerContentView.axis = UILayoutConstraintAxisVertical;
+    self.innerContentView.layer.borderWidth = 1;
 
     // Adds the empty background image.
     _backgroundImageView = [[HomeCustomizationImageView alloc] init];
@@ -164,6 +165,10 @@
     [self registerForTraitChanges:
               @[ NewTabPageTrait.class, NewTabPageImageBackgroundTrait.class ]
                        withAction:@selector(applyTheme)];
+
+    [self registerForTraitChanges:@[ UITraitUserInterfaceStyle.class ]
+                       withAction:@selector(updateCGColors)];
+    [self updateCGColors];
   }
   return self;
 }
@@ -339,4 +344,11 @@
   _feedsView.backgroundColor = [UIColor colorNamed:kBackgroundColor];
 }
 
+// Updates CGColors when the user interface style changes, as they do not
+// update automatically.
+- (void)updateCGColors {
+  self.innerContentView.layer.borderColor =
+      [UIColor colorNamed:kGrey200Color].CGColor;
+}
+
 @end
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_background_color_picker_view_controller.mm b/ios/chrome/browser/home_customization/ui/home_customization_background_color_picker_view_controller.mm
index a19c1deb..83d8b95 100644
--- a/ios/chrome/browser/home_customization/ui/home_customization_background_color_picker_view_controller.mm
+++ b/ios/chrome/browser/home_customization/ui/home_customization_background_color_picker_view_controller.mm
@@ -116,7 +116,12 @@
   self.title = l10n_util::GetNSStringWithFixup(
       IDS_IOS_HOME_CUSTOMIZATION_BACKGROUND_PICKER_COLOR_TITLE);
 
-  self.view.backgroundColor = [UIColor systemBackgroundColor];
+  if (@available(iOS 26, *)) {
+    self.view.backgroundColor = UIColor.clearColor;
+  } else {
+    self.view.backgroundColor =
+        [UIColor colorNamed:kGroupedPrimaryBackgroundColor];
+  }
 
   UICollectionViewFlowLayout* layout = [[CenteredFlowLayout alloc] init];
 
@@ -172,6 +177,7 @@
   _collectionView.dataSource = self;
   _collectionView.delegate = self;
   _collectionView.translatesAutoresizingMaskIntoConstraints = NO;
+  _collectionView.backgroundColor = UIColor.clearColor;
   [self.view addSubview:_collectionView];
 
   [self selectInitialColor];
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_background_picker_cell.mm b/ios/chrome/browser/home_customization/ui/home_customization_background_picker_cell.mm
index c687660..ba23452998 100644
--- a/ios/chrome/browser/home_customization/ui/home_customization_background_picker_cell.mm
+++ b/ios/chrome/browser/home_customization/ui/home_customization_background_picker_cell.mm
@@ -62,7 +62,8 @@
 }
 
 - (void)applyTheme {
-  self.innerContentView.backgroundColor = [UIColor colorNamed:kGrey200Color];
+  self.innerContentView.backgroundColor =
+      [UIColor colorNamed:kGroupedSecondaryBackgroundColor];
 }
 
 #pragma mark - Private
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_background_preset_gallery_picker_view_controller.mm b/ios/chrome/browser/home_customization/ui/home_customization_background_preset_gallery_picker_view_controller.mm
index 7e3d9e1..ea368970 100644
--- a/ios/chrome/browser/home_customization/ui/home_customization_background_preset_gallery_picker_view_controller.mm
+++ b/ios/chrome/browser/home_customization/ui/home_customization_background_preset_gallery_picker_view_controller.mm
@@ -23,8 +23,10 @@
 #import "ios/chrome/browser/home_customization/ui/home_customization_view_controller_protocol.h"
 #import "ios/chrome/browser/home_customization/utils/home_customization_constants.h"
 #import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_image_background_trait.h"
+#import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/chrome/browser/shared/ui/util/custom_ui_trait_accessor.h"
 #import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui/util/constraints_ui_util.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ui/base/l10n/l10n_util_mac.h"
@@ -115,7 +117,12 @@
   self.title = l10n_util::GetNSStringWithFixup(
       IDS_IOS_HOME_CUSTOMIZATION_BACKGROUND_PICKER_PRESET_GALLERY_TITLE);
 
-  self.view.backgroundColor = [UIColor systemBackgroundColor];
+  if (@available(iOS 26, *)) {
+    self.view.backgroundColor = UIColor.clearColor;
+  } else {
+    self.view.backgroundColor =
+        [UIColor colorNamed:kGroupedPrimaryBackgroundColor];
+  }
 
   UICollectionViewCompositionalLayout* layout =
       [[UICollectionViewCompositionalLayout alloc]
@@ -134,6 +141,7 @@
   _collectionView.accessibilityIdentifier =
       kHomeCustomizationGalleryPickerViewAccessibilityIdentifier;
   _collectionView.prefetchDataSource = self;
+  _collectionView.backgroundColor = UIColor.clearColor;
 
   _diffableDataSource = [[UICollectionViewDiffableDataSource alloc]
       initWithCollectionView:_collectionView
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_custom_color_cell.mm b/ios/chrome/browser/home_customization/ui/home_customization_custom_color_cell.mm
index a0ea2ee..f0983b1 100644
--- a/ios/chrome/browser/home_customization/ui/home_customization_custom_color_cell.mm
+++ b/ios/chrome/browser/home_customization/ui/home_customization_custom_color_cell.mm
@@ -56,6 +56,7 @@
     _innerContentView = [[UIView alloc] init];
     _innerContentView.translatesAutoresizingMaskIntoConstraints = NO;
     _innerContentView.layer.masksToBounds = YES;
+    _innerContentView.layer.borderWidth = 1;
     [_borderWrapperView addSubview:_innerContentView];
 
     // Eye dropper icon.
@@ -78,6 +79,10 @@
       [_symbolImageView.centerYAnchor
           constraintEqualToAnchor:self.contentView.centerYAnchor],
     ]];
+
+    [self registerForTraitChanges:@[ UITraitUserInterfaceStyle.class ]
+                       withAction:@selector(updateCGColors)];
+    [self updateCGColors];
   }
   return self;
 }
@@ -119,4 +124,11 @@
   }
 }
 
+// Updates CGColors when the user interface style changes, as they do not
+// update automatically.
+- (void)updateCGColors {
+  _innerContentView.layer.borderColor =
+      [UIColor colorNamed:kGrey200Color].CGColor;
+}
+
 @end
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_toggle_cell.mm b/ios/chrome/browser/home_customization/ui/home_customization_toggle_cell.mm
index faf4888..c10563a 100644
--- a/ios/chrome/browser/home_customization/ui/home_customization_toggle_cell.mm
+++ b/ios/chrome/browser/home_customization/ui/home_customization_toggle_cell.mm
@@ -67,7 +67,8 @@
   self = [super initWithFrame:frame];
   if (self) {
     // Configure `contentView`, representing the overall container.
-    self.contentView.backgroundColor = [UIColor colorNamed:kGrey100Color];
+    self.contentView.backgroundColor =
+        [UIColor colorNamed:kGroupedSecondaryBackgroundColor];
     self.contentView.layer.cornerRadius = kContainerBorderRadius;
     self.contentView.layoutMargins = UIEdgeInsetsMake(
         kVerticalMargin, kHorizontalMargin, kVerticalMargin, kHorizontalMargin);
diff --git a/ios/chrome/browser/home_customization/ui/home_cutomization_color_palette_cell.mm b/ios/chrome/browser/home_customization/ui/home_cutomization_color_palette_cell.mm
index 1989c41..147743f7 100644
--- a/ios/chrome/browser/home_customization/ui/home_cutomization_color_palette_cell.mm
+++ b/ios/chrome/browser/home_customization/ui/home_cutomization_color_palette_cell.mm
@@ -62,6 +62,7 @@
     _innerContentView = [[UIView alloc] init];
     _innerContentView.translatesAutoresizingMaskIntoConstraints = NO;
     _innerContentView.layer.masksToBounds = YES;
+    _innerContentView.layer.borderWidth = 1;
     [_borderWrapperView addSubview:_innerContentView];
 
     _lightColorView = [[UIView alloc] init];
@@ -117,6 +118,10 @@
     AddSameConstraints(_borderWrapperView, self.contentView);
     AddSameConstraintsWithInset(_innerContentView, _borderWrapperView,
                                 kGapBorderWidth + kHighlightBorderWidth);
+
+    [self registerForTraitChanges:@[ UITraitUserInterfaceStyle.class ]
+                       withAction:@selector(updateCGColors)];
+    [self updateCGColors];
   }
   return self;
 }
@@ -160,4 +165,11 @@
   }
 }
 
+// Updates CGColors when the user interface style changes, as they do not
+// update automatically.
+- (void)updateCGColors {
+  _innerContentView.layer.borderColor =
+      [UIColor colorNamed:kGrey200Color].CGColor;
+}
+
 @end
diff --git a/ios/chrome/browser/lens/ui_bundled/features.h b/ios/chrome/browser/lens/ui_bundled/features.h
index 2789b25..62e45b6 100644
--- a/ios/chrome/browser/lens/ui_bundled/features.h
+++ b/ios/chrome/browser/lens/ui_bundled/features.h
@@ -79,4 +79,7 @@
 // Whether to enable the Strokes API for Lens.
 BASE_DECLARE_FEATURE(kLensStrokesAPIEnabled);
 
+// Whether to enable the Shader V2 for Lens Omnient.
+BASE_DECLARE_FEATURE(kLensOmnientShaderV2Enabled);
+
 #endif  // IOS_CHROME_BROWSER_LENS_UI_BUNDLED_FEATURES_H_
diff --git a/ios/chrome/browser/lens/ui_bundled/features.mm b/ios/chrome/browser/lens/ui_bundled/features.mm
index b4bf3cf..e5632a7 100644
--- a/ios/chrome/browser/lens/ui_bundled/features.mm
+++ b/ios/chrome/browser/lens/ui_bundled/features.mm
@@ -72,3 +72,5 @@
 BASE_FEATURE(kLensTripleCameraEnabled, base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kLensStrokesAPIEnabled, base::FEATURE_DISABLED_BY_DEFAULT);
+
+BASE_FEATURE(kLensOmnientShaderV2Enabled, base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/ios/chrome/browser/location_bar/badge/coordinator/location_bar_badge_mediator.mm b/ios/chrome/browser/location_bar/badge/coordinator/location_bar_badge_mediator.mm
index 768c02d..f43e85b 100644
--- a/ios/chrome/browser/location_bar/badge/coordinator/location_bar_badge_mediator.mm
+++ b/ios/chrome/browser/location_bar/badge/coordinator/location_bar_badge_mediator.mm
@@ -29,9 +29,10 @@
 
 namespace {
 
-// Time to transition in seconds.
-const int kTransitionTimeInSeconds = 2;
-
+// Time to start transition in seconds.
+const int kStartExpandTransitionTimeInSeconds = 2;
+// Time to start the collapse transition in seconds.
+const int kStartCollapseTransitionTimeInSeconds = 5;
 }  // anonymous namespace
 
 @interface LocationBarBadgeMediator () <CRWWebStateObserver,
@@ -204,7 +205,8 @@
 - (void)startPromoTimer {
   __weak LocationBarBadgeMediator* weakSelf = self;
   _promoStartTimer = std::make_unique<base::OneShotTimer>();
-  _promoStartTimer->Start(FROM_HERE, base::Seconds(kTransitionTimeInSeconds),
+  _promoStartTimer->Start(FROM_HERE,
+                          base::Seconds(kStartExpandTransitionTimeInSeconds),
                           base::BindOnce(^{
                             [weakSelf setupAndExpandChip];
                           }));
@@ -227,7 +229,8 @@
   __weak LocationBarBadgeMediator* weakSelf = self;
 
   _promoEndTimer = std::make_unique<base::OneShotTimer>();
-  _promoEndTimer->Start(FROM_HERE, base::Seconds(kTransitionTimeInSeconds),
+  _promoEndTimer->Start(FROM_HERE,
+                        base::Seconds(kStartCollapseTransitionTimeInSeconds),
                         base::BindOnce(^{
                           [weakSelf cleanupAndTransitionToDefaultBadgeState];
                         }));
diff --git a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator_unittest.mm b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator_unittest.mm
index 79233dcd..c7ca3ddb 100644
--- a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator_unittest.mm
+++ b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator_unittest.mm
@@ -11,8 +11,8 @@
 #import "ios/chrome/browser/bookmarks/model/bookmark_model_factory.h"
 #import "ios/chrome/browser/browser_view/model/browser_view_visibility_notifier_browser_agent.h"
 #import "ios/chrome/browser/commerce/model/shopping_service_factory.h"
-#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_action_item.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_most_visited_item.h"
+#import "ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_shortcut_item.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_coordinator.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_mediator.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_view_controller.h"
@@ -449,8 +449,8 @@
   histogram_tester_->ExpectTotalCount(kStartTimeSpentHistogram, 0);
   histogram_tester_->ExpectTotalCount(kStartImpressionHistogram, 1);
 
-  ContentSuggestionsMostVisitedActionItem* item =
-      [[ContentSuggestionsMostVisitedActionItem alloc] init];
+  ContentSuggestionsShortcutItem* item =
+      [[ContentSuggestionsShortcutItem alloc] init];
   item.title = @"Bookmarks 0";
   [coordinator_ shortcutTileOpened];
   // Force the URL load callback to simulate the NavigationManager receiving the
diff --git a/ios/chrome/browser/providers/bwg/chromium_bwg.mm b/ios/chrome/browser/providers/bwg/chromium_bwg.mm
index 5c49e93..0804bce 100644
--- a/ios/chrome/browser/providers/bwg/chromium_bwg.mm
+++ b/ios/chrome/browser/providers/bwg/chromium_bwg.mm
@@ -9,16 +9,6 @@
 // Script to check whether PageContext should be detached from the request.
 constexpr const char16_t* kShouldDetachPageContextScript = u"return false;";
 
-std::string CreateRequestBody(
-    std::string prompt,
-    std::unique_ptr<optimization_guide::proto::PageContext> page_context) {
-  return std::string();
-}
-
-std::unique_ptr<network::ResourceRequest> CreateResourceRequest() {
-  return nullptr;
-}
-
 void StartBwgOverlay(BWGConfiguration* bwg_configuration) {}
 
 const std::u16string GetPageContextShouldDetachScript() {
diff --git a/ios/chrome/browser/safe_browsing/model/chrome_password_protection_service.mm b/ios/chrome/browser/safe_browsing/model/chrome_password_protection_service.mm
index fcd08f92..9ee69fb 100644
--- a/ios/chrome/browser/safe_browsing/model/chrome_password_protection_service.mm
+++ b/ios/chrome/browser/safe_browsing/model/chrome_password_protection_service.mm
@@ -466,7 +466,7 @@
 
 bool ChromePasswordProtectionService::IsPrimaryAccountSignedIn() const {
   return !GetAccountInfo().account_id.empty() &&
-         !GetAccountInfo().hosted_domain.empty();
+         GetAccountInfo().GetHostedDomain().has_value();
 }
 
 bool ChromePasswordProtectionService::IsAccountConsumer(
@@ -477,8 +477,7 @@
   return (username.find("@") != std::string::npos &&
           !signin::AccountManagedStatusFinder::MayBeEnterpriseUserBasedOnEmail(
               username)) ||
-         GetAccountInfoForUsername(username).hosted_domain ==
-             kNoHostedDomainFound;
+         GetAccountInfoForUsername(username).GetHostedDomain() == std::string();
 }
 
 bool ChromePasswordProtectionService::IsInExcludedCountry() {
diff --git a/ios/chrome/browser/settings/ui_bundled/sync/sync_encryption_table_view_controller.mm b/ios/chrome/browser/settings/ui_bundled/sync/sync_encryption_table_view_controller.mm
index 30d4f9dc..d897433 100644
--- a/ios/chrome/browser/settings/ui_bundled/sync/sync_encryption_table_view_controller.mm
+++ b/ios/chrome/browser/settings/ui_bundled/sync/sync_encryption_table_view_controller.mm
@@ -277,6 +277,7 @@
                        checked:(BOOL)checked
                        enabled:(BOOL)enabled {
   TableViewTextItem* item = [[TableViewTextItem alloc] initWithType:type];
+  item.titleNumberOfLines = 0;
   item.accessibilityTraits |= UIAccessibilityTraitButton;
   item.text = text;
   item.accessoryType = checked ? UITableViewCellAccessoryCheckmark
diff --git a/ios/chrome/browser/shared/ui/symbols/symbol_names.h b/ios/chrome/browser/shared/ui/symbols/symbol_names.h
index 6ede74d2..1b29b2f 100644
--- a/ios/chrome/browser/shared/ui/symbols/symbol_names.h
+++ b/ios/chrome/browser/shared/ui/symbols/symbol_names.h
@@ -186,6 +186,7 @@
 extern NSString* const kMagnifyingglassCircleSymbol;
 extern NSString* const kEllipsisCircleFillSymbol;
 extern NSString* const kEllipsisRectangleSymbol;
+extern NSString* const kEllipsisSymbol;
 extern NSString* const kPinSymbol;
 extern NSString* const kPinSlashSymbol;
 extern NSString* const kSettingsSymbol;
diff --git a/ios/chrome/browser/shared/ui/symbols/symbol_names.mm b/ios/chrome/browser/shared/ui/symbols/symbol_names.mm
index 237c5ca..70998a4 100644
--- a/ios/chrome/browser/shared/ui/symbols/symbol_names.mm
+++ b/ios/chrome/browser/shared/ui/symbols/symbol_names.mm
@@ -188,6 +188,7 @@
 NSString* const kMagnifyingglassCircleSymbol = @"magnifyingglass.circle";
 NSString* const kEllipsisCircleFillSymbol = @"ellipsis.circle.fill";
 NSString* const kEllipsisRectangleSymbol = @"ellipsis.rectangle";
+NSString* const kEllipsisSymbol = @"ellipsis";
 NSString* const kPinSymbol = @"pin";
 NSString* const kPinSlashSymbol = @"pin.slash";
 NSString* const kSettingsSymbol = @"gearshape";
diff --git a/ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.h b/ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.h
index eea83df..696f6b2 100644
--- a/ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.h
+++ b/ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.h
@@ -33,6 +33,9 @@
 // Sets the `checked` property in the cell.
 @property(nonatomic, assign) BOOL checked;
 
+// Sets the number of line for the cell title. Default is 1.
+@property(nonatomic, assign) NSInteger titleNumberOfLines;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_SHARED_UI_TABLE_VIEW_CELLS_TABLE_VIEW_TEXT_ITEM_H_
diff --git a/ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.mm b/ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.mm
index 7fab2f7..6da377d5 100644
--- a/ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.mm
+++ b/ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.mm
@@ -22,6 +22,7 @@
   if (self) {
     self.cellClass = [LegacyTableViewCell class];
     _enabled = YES;
+    _titleNumberOfLines = 1;
   }
   return self;
 }
@@ -52,7 +53,7 @@
         self.accessibilityLabel ? self.accessibilityLabel : self.text;
   }
   configuration.titleColor = self.textColor;
-  configuration.titleNumberOfLines = 1;
+  configuration.titleNumberOfLines = self.titleNumberOfLines;
 
   cell.contentConfiguration = configuration;
 
diff --git a/ios/chrome/common/ui/promo_style/promo_style_view_controller.mm b/ios/chrome/common/ui/promo_style/promo_style_view_controller.mm
index 88ea1ee9..e77e593 100644
--- a/ios/chrome/common/ui/promo_style/promo_style_view_controller.mm
+++ b/ios/chrome/common/ui/promo_style/promo_style_view_controller.mm
@@ -207,11 +207,11 @@
   }
 
   if (self.preferToCompressContent) {
-    // Constrain the height of _scrollContentView to the height of _scrollView,
-    // making the content unscrollable.  Set constraint priority to
-    // UILayoutPriorityDefaultLow + 1 so that this constraint is deactivated and
-    // content is made scrollable only after views with compression resistence
-    // of UILayoutPriorityDefaultLow are first compressed.
+    // To make the content unscrollable, constrain the height of the content
+    // view. Set constraint priority to UILayoutPriorityDefaultLow + 1 so that
+    // this constraint is deactivated and content is made scrollable only after
+    // views with compression resistance of UILayoutPriorityDefaultLow are
+    // first compressed.
     NSLayoutConstraint* contentViewUnscrollableHeightConstraint =
         [self.contentView.heightAnchor
             constraintEqualToAnchor:self.view.heightAnchor];
@@ -448,10 +448,10 @@
     self.didReachBottom = YES;
   }
 
-  // Only add the scroll view delegate after all the view layouts are fully
+  // Update views based on the initial scroll state, once the layout is fully
   // done.
   dispatch_async(dispatch_get_main_queue(), ^{
-    [self setupScrollView];
+    [self updateViewsForInitialScrollState];
   });
 }
 
@@ -1026,9 +1026,9 @@
   }];
 }
 
-// Adds the scroll view delegate and sets up the content views. This should be
+// Updates views based on the initial scroll state. This should be
 // done only once all the view layouts are fully done.
-- (void)setupScrollView {
+- (void)updateViewsForInitialScrollState {
   _canUpdateViewsOnScroll = YES;
 
   // At this point, the scroll view has computed its content height. If
diff --git a/ios/chrome/test/earl_grey2/chrome_ios_eg2_test.gni b/ios/chrome/test/earl_grey2/chrome_ios_eg2_test.gni
index a5cf07f1..1e8dc07 100644
--- a/ios/chrome/test/earl_grey2/chrome_ios_eg2_test.gni
+++ b/ios/chrome/test/earl_grey2/chrome_ios_eg2_test.gni
@@ -94,6 +94,8 @@
 
     bundle_identifier = shared_bundle_id_for_test_apps
 
+    link_framework_first = "EarlyMallocZoneRegistration"
+
     if (defined(_tweak_entitlements)) {
       entitlements_target = ":$_tweak_entitlements"
     }
diff --git a/ios/chrome/test/providers/bwg/test_bwg.mm b/ios/chrome/test/providers/bwg/test_bwg.mm
index dcab95f..90731a2 100644
--- a/ios/chrome/test/providers/bwg/test_bwg.mm
+++ b/ios/chrome/test/providers/bwg/test_bwg.mm
@@ -6,16 +6,6 @@
 
 namespace ios::provider {
 
-std::string CreateRequestBody(
-    std::string prompt,
-    std::unique_ptr<optimization_guide::proto::PageContext> page_context) {
-  return std::string();
-}
-
-std::unique_ptr<network::ResourceRequest> CreateResourceRequest() {
-  return nullptr;
-}
-
 void StartBwgOverlay(BWGConfiguration* bwg_configuration) {}
 
 const std::u16string GetPageContextShouldDetachScript() {
diff --git a/ios/chrome/test/xcuitest/ios_chrome_xcuitest_app_host.gni b/ios/chrome/test/xcuitest/ios_chrome_xcuitest_app_host.gni
index e5496e6..9bc00ae 100644
--- a/ios/chrome/test/xcuitest/ios_chrome_xcuitest_app_host.gni
+++ b/ios/chrome/test/xcuitest/ios_chrome_xcuitest_app_host.gni
@@ -33,6 +33,8 @@
 
     bundle_identifier = shared_bundle_id_for_test_apps
 
+    link_framework_first = "EarlyMallocZoneRegistration"
+
     if (!defined(deps)) {
       deps = []
     }
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 8d60857..55796de3 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 @@
-c489c6ef1b1dfa2539895a7917ae39c2b1676adb
\ No newline at end of file
+b0193ae81f68c96ed6a75005e346274d19384435
\ 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 4ab3741..60c02143 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 @@
-ad968f152f4d37c7ebdcffe6dbc2effc46e58fec
\ No newline at end of file
+3860b7535da3fa96baff917c0755a85231548bdf
\ 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 e914b09..9418551 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 @@
-12c61711a3e459f70eba659479bce2eba8dbf1d5
\ No newline at end of file
+f969e1c860daf6b82fdb9377ccea51bcf0d76a09
\ 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 74941ab..c64c736 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 @@
-022149505cd82cda1e7e0f3d22f5588a44b96171
\ No newline at end of file
+04762a414b3635d7c634205ca0e8ecbfd3933270
\ 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 a96876a..c1988ff4 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 @@
-a4891288952331b1b26e55df830a1c0be2dd8a26
\ No newline at end of file
+2aadb6f2e87af0c0d08c19a154e4d95123bdbf10
\ 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 94841e87..570e6cd 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 @@
-fc0cfe5a2aa3a582703a58b252165ddf869bc9ae
\ No newline at end of file
+fb54057e7495fce92cd1651a5e25d0241b4f0cbe
\ 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 0fb39bd..31786e13 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 @@
-bf54ce67f821eff0ceca896f4c31ba96c4aaae31
\ No newline at end of file
+8aea29212f95b7424552584b651d1bab5e0301d6
\ 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 21e78ffb..64cd7221 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 @@
-9c7427ff41c6abc35b469bb13eaee832a9a34807
\ No newline at end of file
+e7e39e0ff75aef97a7d3a51cb6559865c1e6d58d
\ 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 7f14e1b..49154d7 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 @@
-738fab8f431ab10ed04304388d922a111c628329
\ No newline at end of file
+a5deb44b3410fa94dba9375379b74dfb122cb9e2
\ 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 b049a242..0c7cd14 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 @@
-62df44a67010f26c03665768c274d5f48071497c
\ No newline at end of file
+c7341853a711b7542b651e293408416aa48c6f65
\ 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 af652370..f0f8f887 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 @@
-88dd126a038e1538a019f4b969645fe8717b0b2f
\ No newline at end of file
+65fdd1139f4304c12f9f982b2efd5eee0c7312c7
\ 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 cbc96a6..2a5e0e6 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 @@
-a2304404c4e0462f7192e86221c0dd8839be8fe3
\ No newline at end of file
+8cd04e863f5d4f8fe89167300c9307634bb792ee
\ 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 546a508..671939b9 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 @@
-2b21ad789f0250da8bfc14ff0cfc282ed4c6ebad
\ No newline at end of file
+d61d4f012ee0cadf7cd79dad7eeac9d8ce087772
\ 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 0f522f9..f10de0b 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 @@
-0caab44dc09c409ddd44c0cf7429e1d90cfc270c
\ No newline at end of file
+f1e40dc52e29bebb7489174669d5b2acbba4384f
\ 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 c540b08..bc0cead 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 @@
-83a49475c2ad36faba2cb1d56bd542fccd831426
\ No newline at end of file
+624a84fff6499187ddbab7a1b1f61a800d3ad16c
\ 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 a92b01ab..66a3829f 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 @@
-c7a33efe53ba6d67113cc941a6cb6f796cb2db9b
\ No newline at end of file
+3c45933f99d9f71e98a441dceb33188cee289ebf
\ 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 1991859..084f728 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 @@
-d32fcf92c88e545da16a62d64bcef5c8c3c8e6be
\ No newline at end of file
+873145ac80a9fc293bcce759790750333942bd11
\ 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 2c98c1c..e2a7c93 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 @@
-701a483877489dfc13822c8b540ce51e04cefc80
\ No newline at end of file
+8caf7c724918fe3a6b4bbd3b38147ea0c435e509
\ 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 44de62d..d7e56bb 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 @@
-5e116fc4eafd9530dcc8197e90295b0018930ae8
\ No newline at end of file
+0a7a1158d8ced6e3d62aee7289f5e092721114cd
\ 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 bdd20916..407190c8 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 @@
-4caa7cb460475172b401674b7f2c5ef52960a7c4
\ No newline at end of file
+ff799563eedeeebbe649edccf212d3306703e3e8
\ 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 e1b25f46..21f1c4c 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 @@
-b43bb80360b0bcc98ad5e57d2b23f50ea3d12810
\ No newline at end of file
+d88dda3927989c5aa48e7e990e1cc51debfca37b
\ 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 27df52f7..b318f26 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 @@
-7bf5553dbda13944716c38a36cc7ba99699e8d5e
\ No newline at end of file
+6d85b39d4a6457d03ee4355e34e815aea503fd5e
\ 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 fbf6eec..dd6c6279 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 @@
-3fe4033e39f3a0a5fa6360994fc44c78ebcb1dd4
\ No newline at end of file
+a5e2cab36a0a28a695bb767511e244a8a11d91e7
\ 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 ab0a572..0e50a90a 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 @@
-361fbd3448071c5a201affd7b4c4fa67f9d39a80
\ No newline at end of file
+3442ea09ce671a33b9752728c8517bc859832e0f
\ 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 f1d0a42..a68a51e6 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 @@
-19f72dd9efaca440876b557cd23cd5c332eced2a
\ No newline at end of file
+c3a04ff79e6d16431dc91adff6faa8ccf8039959
\ 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 8b6137d..5616c74 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 @@
-fcfa98c3c6f7d990ce776e5355ab617346eb235a
\ No newline at end of file
+8505e43d6298882fd8d7f30e510185149bb4a523
\ 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 3a6f5474..ba35a8d3 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 @@
-64900848b31f207c6826b3a276fa02b056696a32
\ No newline at end of file
+2952aa78571659c382161b9fc48e38245fc0ebfb
\ 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 b01d125..1851bc2df 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 @@
-3b5c2af2f7d01c4612c7f7b187b3380390d278d7
\ No newline at end of file
+009a8b5109d147aa1db83c2043f7602fc08fafc9
\ No newline at end of file
diff --git a/ios/public/provider/chrome/browser/bwg/bwg_api.h b/ios/public/provider/chrome/browser/bwg/bwg_api.h
index cdeba21..3bceda2 100644
--- a/ios/public/provider/chrome/browser/bwg/bwg_api.h
+++ b/ios/public/provider/chrome/browser/bwg/bwg_api.h
@@ -77,14 +77,6 @@
   kEnterpriseDisabled,
 };
 
-// Creates request body data using a prompt and page context.
-std::string CreateRequestBody(
-    std::string prompt,
-    std::unique_ptr<optimization_guide::proto::PageContext> page_context);
-
-// Creates resource request for loading glic.
-std::unique_ptr<network::ResourceRequest> CreateResourceRequest();
-
 // Starts the overlay experience with the given configuration.
 void StartBwgOverlay(BWGConfiguration* bwg_configuration);
 
diff --git a/ios/web/content/init/ios_content_main_runner.cc b/ios/web/content/init/ios_content_main_runner.cc
index 79be3b6e..72862c6 100644
--- a/ios/web/content/init/ios_content_main_runner.cc
+++ b/ios/web/content/init/ios_content_main_runner.cc
@@ -2,11 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifdef UNSAFE_BUFFERS_BUILD
-// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
-#pragma allow_unsafe_buffers
-#endif
-
 #import "ios/web/content/init/ios_content_main_runner.h"
 
 #import "base/types/fixed_array.h"
@@ -24,11 +19,7 @@
 void IOSContentMainRunner::Initialize(WebMainParams params) {
   static crash_reporter::CrashKeyString<4> key("blink");
   key.Set("yes");
-  argv_.resize(params.argc);
-  const char* const* argv = params.argv;
-  for (int i = 0; i < params.argc; ++i) {
-    argv_[i].assign(argv[i]);
-  }
+  argv_ = std::move(params.args);
 }
 
 int IOSContentMainRunner::Startup() {
diff --git a/ios/web/init/ios_global_state.mm b/ios/web/init/ios_global_state.mm
index 6d8c919c..70b1071 100644
--- a/ios/web/init/ios_global_state.mm
+++ b/ios/web/init/ios_global_state.mm
@@ -23,14 +23,17 @@
 
 namespace ios_global_state {
 
+CreateParams::CreateParams() = default;
+
+CreateParams::~CreateParams() = default;
+
 void Create(const CreateParams& create_params) {
   static dispatch_once_t once_token;
   dispatch_once(&once_token, ^{
     if (create_params.install_at_exit_manager) {
       g_exit_manager = new base::AtExitManager();
     }
-    base::CommandLine::Init(create_params.argc, create_params.argv);
-
+    base::CommandLine::Init(create_params.args);
     base::ThreadPoolInstance::Create("Browser");
   });
 }
diff --git a/ios/web/init/web_main.mm b/ios/web/init/web_main.mm
index 41fbb75..9adfdfb 100644
--- a/ios/web/init/web_main.mm
+++ b/ios/web/init/web_main.mm
@@ -11,7 +11,7 @@
 WebMainParams::WebMainParams() : WebMainParams(nullptr) {}
 
 WebMainParams::WebMainParams(WebMainDelegate* delegate)
-    : delegate(delegate), register_exit_manager(true), argc(0), argv(nullptr) {}
+    : delegate(delegate), register_exit_manager(true) {}
 
 WebMainParams::~WebMainParams() = default;
 
diff --git a/ios/web/init/web_main_runner_impl.mm b/ios/web/init/web_main_runner_impl.mm
index 8e050f588..8a2cf81b 100644
--- a/ios/web/init/web_main_runner_impl.mm
+++ b/ios/web/init/web_main_runner_impl.mm
@@ -36,8 +36,7 @@
 
   ios_global_state::CreateParams create_params;
   create_params.install_at_exit_manager = params.register_exit_manager;
-  create_params.argc = params.argc;
-  create_params.argv = params.argv;
+  create_params.args = std::move(params.args);
   ios_global_state::Create(create_params);
   web::WebThreadImpl::CreateTaskExecutor();
 
diff --git a/ios/web/public/init/ios_global_state.h b/ios/web/public/init/ios_global_state.h
index c45c42e..b878d23 100644
--- a/ios/web/public/init/ios_global_state.h
+++ b/ios/web/public/init/ios_global_state.h
@@ -5,7 +5,9 @@
 #ifndef IOS_WEB_PUBLIC_INIT_IOS_GLOBAL_STATE_H_
 #define IOS_WEB_PUBLIC_INIT_IOS_GLOBAL_STATE_H_
 
-#import "base/memory/raw_ptr.h"
+#include <string>
+#include <vector>
+
 #include "base/task/thread_pool/thread_pool_instance.h"
 
 namespace base {
@@ -16,12 +18,10 @@
 
 // Contains parameters passed to `Create`.
 struct CreateParams {
-  CreateParams() : install_at_exit_manager(false), argc(0), argv(nullptr) {}
-
-  bool install_at_exit_manager;
-
-  int argc;
-  raw_ptr<const char*> argv;
+  CreateParams();
+  ~CreateParams();
+  bool install_at_exit_manager = false;
+  std::vector<std::string> args;
 };
 
 // Creates global state for iOS. This should be called as early as possible in
diff --git a/ios/web/public/init/web_main.h b/ios/web/public/init/web_main.h
index f924d3a..cad5ef5fb 100644
--- a/ios/web/public/init/web_main.h
+++ b/ios/web/public/init/web_main.h
@@ -6,8 +6,10 @@
 #define IOS_WEB_PUBLIC_INIT_WEB_MAIN_H_
 
 #include <memory>
+#include <string>
+#include <vector>
 
-#import "base/memory/raw_ptr.h"
+#include "base/memory/raw_ptr.h"
 #include "ios/web/public/init/web_main_delegate.h"
 
 namespace web {
@@ -29,10 +31,8 @@
 
   raw_ptr<WebMainDelegate> delegate;
 
-  bool register_exit_manager;
-
-  int argc;
-  raw_ptr<const char*> argv;
+  bool register_exit_manager = true;
+  std::vector<std::string> args;
 };
 
 // Encapsulates any setup and initialization that is needed by common
diff --git a/media/base/android/media_drm_bridge.cc b/media/base/android/media_drm_bridge.cc
index 59a42ede..868f63e 100644
--- a/media/base/android/media_drm_bridge.cc
+++ b/media/base/android/media_drm_bridge.cc
@@ -24,6 +24,7 @@
 #include "base/functional/callback_helpers.h"
 #include "base/location.h"
 #include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/string_number_conversions.h"
@@ -324,6 +325,7 @@
 int GetFirstApiLevel() {
   JNIEnv* env = AttachCurrentThread();
   int first_api_level = Java_MediaDrmBridge_getFirstApiLevel(env);
+  base::UmaHistogramSparse("Media.EME.MediaDrm.FirstApiLevel", first_api_level);
   return first_api_level;
 }
 
@@ -377,10 +379,17 @@
     return true;
   }
 
-  // If "ro.product.first_api_level" does not match, then check build number.
-  DVLOG(1) << "api_level = " << base::android::android_info::sdk_int();
-  return base::android::android_info::sdk_int() >=
-         base::android::android_info::SDK_VERSION_OREO;
+  if (first_api_level == 0) {
+    // If "ro.product.first_api_level" is 0, that means it is unset, and does
+    // not exist. We should then verify against the build number, as that is
+    // what seems to communicate the first api level on devices that were
+    // released before "ro.product.first_api_level" was introduced.
+    DVLOG(1) << "api_level = " << base::android::android_info::sdk_int();
+    return base::android::android_info::sdk_int() >=
+           base::android::android_info::SDK_VERSION_OREO;
+  }
+
+  return false;
 }
 
 // static
diff --git a/media/renderers/win/media_foundation_renderer.cc b/media/renderers/win/media_foundation_renderer.cc
index 61749496..e9864ae7 100644
--- a/media/renderers/win/media_foundation_renderer.cc
+++ b/media/renderers/win/media_foundation_renderer.cc
@@ -49,7 +49,54 @@
 namespace {
 
 ATOM g_video_window_class = 0;
-constexpr uint32_t kAmdVendorId = 0x1002;
+
+// GPU vendor IDs
+constexpr uint32_t kGpuVendorIdIntel = 0x8086;
+constexpr uint32_t kGpuVendorIdNvidia = 0x10de;
+constexpr uint32_t kGpuVendorIdAmd = 0x1002;
+constexpr uint32_t kGpuVendorIdNone = 0x0000;
+
+constexpr uint32_t kGpuBitmaskIntel = 0x001;
+constexpr uint32_t kGpuBitmaskNvidia = 0x001 << 1;
+constexpr uint32_t kGpuBitmaskAmd = 0x001 << 2;
+constexpr uint32_t kGpuBitmaskOther = 0x001 << 3;
+
+constexpr uint32_t kMakeGpuNonActive = 4;
+
+// Reported to UMA. Do NOT change or reuse existing values.
+enum class GpuOrDisplayCount {
+  kUnknown = 0,
+  kOne = 1,
+  kTwoOrMore = 2,  // We don't care if more than 2 gpus are present. So single
+                   // and two or more is enough.
+  kMaxValue = kTwoOrMore
+};
+
+// Reported to UMA. Do NOT change or reuse existing values.
+enum class ActiveGpuInfo : uint32_t {
+  kNone = 0,
+  kIntel = kGpuBitmaskIntel,
+  kNvidia = kGpuBitmaskNvidia,
+  kAmd = kGpuBitmaskAmd,
+  kOther = kGpuBitmaskOther,
+  kIntelIntel = kIntel | (kIntel << kMakeGpuNonActive),
+  kNvidiaIntel = kNvidia | (kIntel << kMakeGpuNonActive),
+  kAmdIntel = kAmd | (kIntel << kMakeGpuNonActive),
+  kOtherIntel = kOther | (kIntel << kMakeGpuNonActive),
+  kIntelNvidia = kIntel | (kNvidia << kMakeGpuNonActive),
+  kNvidiaNvidia = kNvidia | (kNvidia << kMakeGpuNonActive),
+  kAmdNvidia = kAmd | (kNvidia << kMakeGpuNonActive),
+  kOtherNvidia = kOther | (kNvidia << kMakeGpuNonActive),
+  kIntelAmd = kIntel | (kAmd << kMakeGpuNonActive),
+  kNvidiaAmd = kNvidia | (kAmd << kMakeGpuNonActive),
+  kAmdAmd = kAmd | (kAmd << kMakeGpuNonActive),
+  kOtherAmd = kOther | (kAmd << kMakeGpuNonActive),
+  kIntelOther = kIntel | (kOther << kMakeGpuNonActive),
+  kNvidiaOther = kNvidia | (kOther << kMakeGpuNonActive),
+  kAmdOther = kAmd | (kOther << kMakeGpuNonActive),
+  kOtherOther = kOther | (kOther << kMakeGpuNonActive),
+  kMaxValue = kOtherOther
+};
 
 // The |g_video_window_class| atom obtained is used as the |lpClassName|
 // parameter in CreateWindowEx().
@@ -117,28 +164,153 @@
   return handle == INVALID_HANDLE_VALUE || handle == nullptr;
 }
 
-bool GetVendorIdFromD3D11Device(ID3D11Device* d3d11_device) {
-  DCHECK(d3d11_device);
+std::tuple<uint32_t, LUID> GetVendorIdAndLUIDFromD3D11Device(
+    IMFDXGIDeviceManager* dxgi_device_manager) {
+  DCHECK(dxgi_device_manager);
+
+  DXGIDeviceScopedHandle dxgi_device_handle(dxgi_device_manager);
+  ComPtr<ID3D11Device> d3d11_device = dxgi_device_handle.GetDevice();
+  if (!d3d11_device) {
+    return {kGpuVendorIdNone, {}};
+  }
 
   ComPtr<IDXGIDevice> dxgi_device;
   HRESULT hr = d3d11_device->QueryInterface(IID_PPV_ARGS(&dxgi_device));
   if (FAILED(hr)) {
-    return 0;
+    return {kGpuVendorIdNone, {}};
   }
 
   ComPtr<IDXGIAdapter> adapter;
   hr = dxgi_device->GetAdapter(&adapter);
   if (FAILED(hr)) {
-    return 0;
+    return {kGpuVendorIdNone, {}};
   }
 
   DXGI_ADAPTER_DESC desc = {};
   hr = adapter->GetDesc(&desc);
   if (FAILED(hr)) {
-    return 0;
+    return {kGpuVendorIdNone, {}};
   }
 
-  return desc.VendorId;
+  return {desc.VendorId, desc.AdapterLuid};
+}
+
+uint32_t GpuVendorIdToBitmask(const uint32_t vendor_id) {
+  if (vendor_id == kGpuVendorIdIntel) {
+    return kGpuBitmaskIntel;
+  }
+  if (vendor_id == kGpuVendorIdNvidia) {
+    return kGpuBitmaskNvidia;
+  }
+  if (vendor_id == kGpuVendorIdAmd) {
+    return kGpuBitmaskAmd;
+  }
+  if (vendor_id == kGpuVendorIdNone) {
+    return 0;
+  }
+  return kGpuBitmaskOther;
+}
+
+// Get non-active GPU vendor IDs.
+std::vector<uint32_t> GetNonActiveGpuVendorIds(const LUID& active_gpu_luid) {
+  std::vector<uint32_t> vendor_ids;
+  Microsoft::WRL::ComPtr<IDXGIFactory1> dxgi_factory;
+  if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory)))) {
+    return vendor_ids;
+  }
+
+  Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
+  for (UINT i = 0; SUCCEEDED(dxgi_factory->EnumAdapters(i, &adapter)); ++i) {
+    DXGI_ADAPTER_DESC adapter_desc;
+    if (SUCCEEDED(adapter->GetDesc(&adapter_desc))) {
+      if (adapter_desc.AdapterLuid.HighPart == active_gpu_luid.HighPart &&
+          adapter_desc.AdapterLuid.LowPart == active_gpu_luid.LowPart) {
+        continue;
+      }
+      vendor_ids.push_back(adapter_desc.VendorId);
+      DVLOG(3) << __func__ << ": Adapter " << i << " Vendor ID: 0x" << std::hex
+               << adapter_desc.VendorId;
+    }
+    adapter.Reset();
+  }
+  return vendor_ids;
+}
+
+// Callback function that EnumDisplayMonitors calls for each monitor.
+BOOL CALLBACK MyMonitorEnumProc(
+    HMONITOR hMonitor,   // Handle to display monitor
+    HDC hdcMonitor,      // Handle to monitor DC
+    LPRECT lprcMonitor,  // Monitor intersection rectangle
+    LPARAM dwData        // Data passed from EnumDisplayMonitors
+) {
+  if (!dwData) {
+    return FALSE;
+  }
+  // Cast dwData back to the integer pointer we passed in.
+  int* monitorCount = reinterpret_cast<int*>(dwData);
+  // Increment the count for each monitor found.
+  (*monitorCount)++;
+  return TRUE;  // Return TRUE to continue the enumeration.
+}
+
+// Get the total number of attached displays.
+int GetTotalDisplayCount() {
+  int count = 0;
+  BOOL result = EnumDisplayMonitors(nullptr, nullptr, MyMonitorEnumProc,
+                                    reinterpret_cast<LPARAM>(&count));
+  if (!result) {
+    // This case is unlikely for standard usage but good to be aware of.
+    DVLOG(1) << "EnumDisplayMonitors failed: " << GetLastError();
+    return -1;  // Indicate an error
+  }
+  return count;
+}
+
+void ReportGpuInfoUma(const std::string& uma_prefix,
+                      IMFDXGIDeviceManager* dxgi_device_manager) {
+  // For some tests, this can be nullptr.
+  if (!dxgi_device_manager) {
+    return;
+  }
+
+  const auto [active_gpu_vendor_id, active_gpu_luid] =
+      GetVendorIdAndLUIDFromD3D11Device(dxgi_device_manager);
+  if (active_gpu_vendor_id == kGpuVendorIdNone &&
+      active_gpu_luid.LowPart == 0 && active_gpu_luid.HighPart == 0) {
+    DVLOG(1) << __func__ << ": Failed to get active GPU info.";
+    base::UmaHistogramEnumeration(uma_prefix + ".GpuCount",
+                                  GpuOrDisplayCount::kUnknown);
+    base::UmaHistogramEnumeration(uma_prefix + ".ActiveGpuInfo",
+                                  ActiveGpuInfo::kNone);
+  } else {
+    const auto all_nonactive_gpus = GetNonActiveGpuVendorIds(active_gpu_luid);
+    const auto nonactive_gpu_count = all_nonactive_gpus.size();
+    const auto nonactive_gpu_id =
+        nonactive_gpu_count > 0 ? all_nonactive_gpus[0] : kGpuVendorIdNone;
+    const auto active_gpu_info = static_cast<ActiveGpuInfo>(
+        GpuVendorIdToBitmask(active_gpu_vendor_id) |
+        (GpuVendorIdToBitmask(nonactive_gpu_id) << kMakeGpuNonActive));
+
+    DVLOG(3) << __func__ << ": nonactive_gpu_count=" << nonactive_gpu_count
+             << ", active_gpu_vendor_id=" << active_gpu_vendor_id
+             << ", nonactive_gpu_id=" << nonactive_gpu_id
+             << ", active_gpu_info=" << static_cast<uint32_t>(active_gpu_info);
+
+    base::UmaHistogramEnumeration(uma_prefix + ".GpuCount",
+                                  nonactive_gpu_count > 0
+                                      ? GpuOrDisplayCount::kTwoOrMore
+                                      : GpuOrDisplayCount::kOne);
+    base::UmaHistogramEnumeration(uma_prefix + ".ActiveGpuInfo",
+                                  active_gpu_info);
+  }
+
+  const auto display_count = GetTotalDisplayCount();
+  DVLOG(3) << __func__ << ": display_count=" << display_count;
+  base::UmaHistogramEnumeration(
+      uma_prefix + ".DisplayCount",
+      display_count == -1 ? GpuOrDisplayCount::kUnknown
+                          : (display_count > 1 ? GpuOrDisplayCount::kTwoOrMore
+                                               : GpuOrDisplayCount::kOne));
 }
 
 std::string RenderedVideoFrameDetectionResultToString(
@@ -994,6 +1166,8 @@
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
   base::UmaHistogramSparse("Media.MediaFoundationRenderer.PlaybackError", hr);
+  ReportGpuInfoUma("Media.MediaFoundationRenderer.PlaybackError",
+                   dxgi_device_manager_.Get());
 
   StopSendingStatistics(StopSendingStatisticsReason::kPlaybackError);
   OnError(status, ErrorReason::kOnPlaybackError, hr);
@@ -1203,15 +1377,10 @@
   // sleep mode and during hotplug, but should be treated the same as
   // DRM_E_TEE_INVALID_HWDRM_STATE.
   if (hresult == DRM_OEM_E_ASD_ACTIVE_DISPLAY_FAIL) {
-    uint32_t vendor_id{0};
     // Attempt to get the vendor_id using the dxgi device.
-    DXGIDeviceScopedHandle dxgi_device_handle(dxgi_device_manager_.Get());
-    ComPtr<ID3D11Device> d3d11_device = dxgi_device_handle.GetDevice();
-    if (d3d11_device) {
-      vendor_id = GetVendorIdFromD3D11Device(d3d11_device.Get());
-    }
-
-    if (vendor_id == kAmdVendorId) {
+    const auto [vendor_id, _] =
+        GetVendorIdAndLUIDFromD3D11Device(dxgi_device_manager_.Get());
+    if (vendor_id == kGpuVendorIdAmd) {
       hresult = DRM_E_TEE_INVALID_HWDRM_STATE;
     }
   }
@@ -1225,6 +1394,9 @@
         "Media.MediaFoundationRenderer.InvalidHwdrmState.VideoFrameDecoded",
         statistics_.video_frames_decoded);
 
+    ReportGpuInfoUma("Media.EME.MediaFoundationService.HardwareContextReset",
+                     dxgi_device_manager_.Get());
+
     new_status = PIPELINE_ERROR_HARDWARE_CONTEXT_RESET;
     if (cdm_proxy_)
       cdm_proxy_->OnHardwareContextReset();
diff --git a/mojo/public/cpp/bindings/lib/array_internal.h b/mojo/public/cpp/bindings/lib/array_internal.h
index 36f2b87..06c39a3 100644
--- a/mojo/public/cpp/bindings/lib/array_internal.h
+++ b/mojo/public/cpp/bindings/lib/array_internal.h
@@ -104,14 +104,7 @@
     return BitRef(&storage[offset / 8],
                   static_cast<uint8_t>(1 << (offset % 8)));
   }
-  static bool ToConstRef(
-      base::span<const StorageType> storage,
-      size_t offset,
-      uint32_t spanification_suspected_redundant_num_elements) {
-    // TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
-    // redundant in M143.
-    CHECK(spanification_suspected_redundant_num_elements == storage.size(),
-          base::NotFatalUntil::M143);
+  static bool ToConstRef(base::span<const StorageType> storage, size_t offset) {
     return (storage[offset / 8] & (1 << (offset % 8))) != 0;
   }
 };
@@ -539,7 +532,7 @@
 
   ConstRef at(size_t offset) const {
     DCHECK(offset < static_cast<size_t>(header_.num_elements));
-    return Traits::ToConstRef(storage(), offset, header_.num_elements);
+    return Traits::ToConstRef(storage(), offset);
   }
 
   StorageType* storage() {
diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
index 780a605..0e38cf4 100644
--- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
+++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
@@ -443,7 +443,7 @@
     SyncEventWatcher watcher(&response->event,
                              base::BindRepeating(set_flag, &signaled));
     const bool* stop_flags[] = {&signaled};
-    watcher.SyncWatch(stop_flags, std::size(stop_flags));
+    watcher.SyncWatch(stop_flags);
   } else {
     // Else we can wait on the event directly. It will only signal after our
     // reply has been processed or cancelled.
diff --git a/mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc b/mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc
index b8d59ce..9e50406ea 100644
--- a/mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc
+++ b/mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc
@@ -160,7 +160,7 @@
 
     // |SyncWatch()| may delete |this|.
     auto weak_self = weak_ptr_factory_.GetWeakPtr();
-    bool result = event_watcher_.SyncWatch(stop_flags, 2);
+    bool result = event_watcher_.SyncWatch(stop_flags);
     if (!weak_self)
       return false;
 
diff --git a/mojo/public/cpp/bindings/lib/sync_event_watcher.cc b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc
index 6d17c7b7..4bf84d0b 100644
--- a/mojo/public/cpp/bindings/lib/sync_event_watcher.cc
+++ b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc
@@ -31,13 +31,7 @@
   IncrementRegisterCount();
 }
 
-bool SyncEventWatcher::SyncWatch(
-    base::span<const bool*> stop_flags,
-    size_t spanification_suspected_redundant_num_stop_flags) {
-  // TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
-  // redundant in M143.
-  CHECK(spanification_suspected_redundant_num_stop_flags == stop_flags.size(),
-        base::NotFatalUntil::M143);
+bool SyncEventWatcher::SyncWatch(base::span<const bool*> stop_flags) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   IncrementRegisterCount();
 
@@ -48,11 +42,9 @@
   constexpr size_t kFlagStackCapacity = 4;
   absl::InlinedVector<const bool*, kFlagStackCapacity> should_stop_array;
   should_stop_array.push_back(&destroyed->data);
-  std::copy(stop_flags.data(),
-            stop_flags.subspan(spanification_suspected_redundant_num_stop_flags)
-                .data(),
+  std::copy(stop_flags.begin(), stop_flags.end(),
             std::back_inserter(should_stop_array));
-  bool result = registry_->Wait(should_stop_array, should_stop_array.size());
+  bool result = registry_->Wait(should_stop_array);
 
   // This object has been destroyed.
   if (destroyed->data)
diff --git a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
index a25b8f9..08680f3 100644
--- a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
+++ b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
@@ -119,12 +119,7 @@
       it->second.get(), std::move(callback));
 }
 
-bool SyncHandleRegistry::Wait(base::span<const bool*> should_stop,
-                              size_t spanification_suspected_redundant_count) {
-  // TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
-  // redundant in M143.
-  CHECK(spanification_suspected_redundant_count == should_stop.size(),
-        base::NotFatalUntil::M143);
+bool SyncHandleRegistry::Wait(base::span<const bool*> should_stop) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   size_t num_ready_handles;
@@ -133,9 +128,10 @@
 
   scoped_refptr<SyncHandleRegistry> preserver(this);
   while (true) {
-    for (size_t i = 0; i < spanification_suspected_redundant_count; ++i) {
-      if (*should_stop[i])
+    for (const bool* flag : should_stop) {
+      if (*flag) {
         return true;
+      }
     }
 
     // TODO(yzshen): Theoretically it can reduce sync call re-entrancy if we
diff --git a/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc
index 841973e..1e90f1afd 100644
--- a/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc
+++ b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc
@@ -46,7 +46,7 @@
   // the boolean that Wait uses.
   auto destroyed = destroyed_;
   const bool* should_stop_array[] = {should_stop, &destroyed->data};
-  bool result = registry_->Wait(should_stop_array, 2);
+  bool result = registry_->Wait(should_stop_array);
 
   // This object has been destroyed.
   if (destroyed->data)
diff --git a/mojo/public/cpp/bindings/sync_event_watcher.h b/mojo/public/cpp/bindings/sync_event_watcher.h
index 0e07659d..d4e4ed78 100644
--- a/mojo/public/cpp/bindings/sync_event_watcher.h
+++ b/mojo/public/cpp/bindings/sync_event_watcher.h
@@ -48,8 +48,7 @@
   //   - returns true when any flag in |stop_flags| is set to |true|.
   //   - return false when any error occurs, including this object being
   //     destroyed during a callback.
-  bool SyncWatch(base::span<const bool*> stop_flags,
-                 size_t spanification_suspected_redundant_num_stop_flags);
+  bool SyncWatch(base::span<const bool*> stop_flags);
 
  private:
   void IncrementRegisterCount();
diff --git a/mojo/public/cpp/bindings/sync_handle_registry.h b/mojo/public/cpp/bindings/sync_handle_registry.h
index ab8353f..5956cfcd 100644
--- a/mojo/public/cpp/bindings/sync_handle_registry.h
+++ b/mojo/public/cpp/bindings/sync_handle_registry.h
@@ -80,8 +80,7 @@
   // The method:
   //   - returns true when any element of |should_stop| is set to true;
   //   - returns false when any error occurs.
-  bool Wait(base::span<const bool*> should_stop,
-            size_t spanification_suspected_redundant_count);
+  bool Wait(base::span<const bool*> should_stop);
 
  private:
   friend class base::RefCounted<SyncHandleRegistry>;
diff --git a/mojo/public/cpp/bindings/tests/sync_handle_registry_unittest.cc b/mojo/public/cpp/bindings/tests/sync_handle_registry_unittest.cc
index 3a93273..a931398 100644
--- a/mojo/public/cpp/bindings/tests/sync_handle_registry_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/sync_handle_registry_unittest.cc
@@ -40,7 +40,7 @@
       registry()->RegisterEvent(&e, base::BindRepeating(callback, &called2));
 
   const bool* stop_flags[] = {&called1, &called2};
-  registry()->Wait(stop_flags, 2);
+  registry()->Wait(stop_flags);
 
   EXPECT_TRUE(called1);
   EXPECT_TRUE(called2);
@@ -49,7 +49,7 @@
   called1 = false;
   called2 = false;
 
-  registry()->Wait(stop_flags, 2);
+  registry()->Wait(stop_flags);
 
   EXPECT_FALSE(called1);
   EXPECT_TRUE(called2);
@@ -82,7 +82,7 @@
           base::BindRepeating([](bool* called) { *called = true; }, &called3));
 
   const bool* stop_flags[] = {&called1, &called2, &called3};
-  registry()->Wait(stop_flags, 3);
+  registry()->Wait(stop_flags);
 
   // We don't make any assumptions about the order in which callbacks run, so
   // we can't check |called1| - it may or may not get set depending on internal
@@ -96,7 +96,7 @@
   called3 = false;
 
   subscription2.reset();
-  registry()->Wait(stop_flags, 3);
+  registry()->Wait(stop_flags);
 
   EXPECT_FALSE(called1);
   EXPECT_FALSE(called2);
@@ -129,14 +129,14 @@
                 base::BindRepeating([](bool* called) { *called = true; },
                                     &nested_called));
         const bool* stop_flag = &nested_called;
-        registry->Wait(base::span_from_ref(stop_flag), 1);
+        registry->Wait(base::span_from_ref(stop_flag));
       },
       &e, &subscription, registry(), &called);
 
   subscription = registry()->RegisterEvent(e.get(), callback);
 
   const bool* stop_flag = &called;
-  registry()->Wait(base::span_from_ref(stop_flag), 1);
+  registry()->Wait(base::span_from_ref(stop_flag));
   EXPECT_TRUE(called);
 }
 
@@ -160,7 +160,7 @@
                 e, base::BindRepeating([](bool* called) { *called = true; },
                                        &nested_called));
         const bool* stop_flag = &nested_called;
-        registry->Wait(base::span_from_ref(stop_flag), 1);
+        registry->Wait(base::span_from_ref(stop_flag));
 
         EXPECT_TRUE(nested_called);
       },
@@ -169,7 +169,7 @@
   subscription = registry()->RegisterEvent(&e, callback);
 
   const bool* stop_flag = &called;
-  registry()->Wait(base::span_from_ref(stop_flag), 1);
+  registry()->Wait(base::span_from_ref(stop_flag));
   EXPECT_TRUE(called);
 }
 
@@ -195,7 +195,7 @@
                                        &called2));
 
         const bool* stop_flag = &called2;
-        registry->Wait(base::span_from_ref(stop_flag), 1);
+        registry->Wait(base::span_from_ref(stop_flag));
       },
       &e, registry(), &called, &call_count);
 
@@ -203,8 +203,7 @@
       registry()->RegisterEvent(&e, callback);
 
   const bool* stop_flag = &called;
-  registry()->Wait(base::span_from_ref(stop_flag), 1);
-
+  registry()->Wait(base::span_from_ref(stop_flag));
   EXPECT_TRUE(called);
   EXPECT_EQ(2, call_count);
 }
@@ -247,7 +246,7 @@
         // been unregistered. This would crash otherwise, since |e1| has been
         // deleted. See http://crbug.com/761097.
         const bool* stop_flags[] = {&called3};
-        registry->Wait(stop_flags, 1);
+        registry->Wait(stop_flags);
 
         EXPECT_TRUE(called3);
       },
@@ -257,7 +256,7 @@
       registry()->RegisterEvent(&e2, callback2);
 
   const bool* stop_flags[] = {&called1, &called2};
-  registry()->Wait(stop_flags, 2);
+  registry()->Wait(stop_flags);
 
   EXPECT_TRUE(called2);
 }
diff --git a/mojo/public/cpp/system/handle_signals_state.h b/mojo/public/cpp/system/handle_signals_state.h
index f5ce4b06..506ae0d 100644
--- a/mojo/public/cpp/system/handle_signals_state.h
+++ b/mojo/public/cpp/system/handle_signals_state.h
@@ -24,6 +24,15 @@
     satisfiable_signals = satisfiable;
   }
 
+  explicit HandleSignalsState(const MojoHandleSignalsState& mojo_state)
+      : MojoHandleSignalsState(mojo_state) {}
+
+  HandleSignalsState& operator=(const MojoHandleSignalsState& mojo_state) {
+    satisfied_signals = mojo_state.satisfied_signals;
+    satisfiable_signals = mojo_state.satisfiable_signals;
+    return *this;
+  }
+
   bool operator==(const HandleSignalsState& other) const {
     return satisfied_signals == other.satisfied_signals &&
            satisfiable_signals == other.satisfiable_signals;
diff --git a/mojo/public/cpp/system/wait_set.cc b/mojo/public/cpp/system/wait_set.cc
index 6d8efd8..a89da8c9 100644
--- a/mojo/public/cpp/system/wait_set.cc
+++ b/mojo/public/cpp/system/wait_set.cc
@@ -138,7 +138,7 @@
             size_t* num_ready_handles,
             base::span<Handle> ready_handles,
             base::span<MojoResult> ready_results,
-            MojoHandleSignalsState* signals_states) {
+            base::span<HandleSignalsState> signals_states) {
     DCHECK(trap_handle_.is_valid());
     DCHECK(num_ready_handles);
     DCHECK(!ready_handles.empty());
@@ -216,8 +216,9 @@
       auto it = ready_handles_.begin();
       ready_handles[i] = it->first;
       ready_results[i] = it->second.result;
-      if (signals_states)
-        UNSAFE_TODO(signals_states[i]) = it->second.signals_state;
+      if (!signals_states.empty()) {
+        signals_states[i] = it->second.signals_state;
+      }
       ready_handles_.erase(it);
     }
 
@@ -361,7 +362,7 @@
                    size_t* num_ready_handles,
                    base::span<Handle> ready_handles,
                    base::span<MojoResult> ready_results,
-                   MojoHandleSignalsState* signals_states) {
+                   base::span<HandleSignalsState> signals_states) {
   state_->Wait(ready_event, num_ready_handles, ready_handles, ready_results,
                signals_states);
 }
diff --git a/mojo/public/cpp/system/wait_set.h b/mojo/public/cpp/system/wait_set.h
index ec53c53..08aa58d 100644
--- a/mojo/public/cpp/system/wait_set.h
+++ b/mojo/public/cpp/system/wait_set.h
@@ -112,7 +112,7 @@
             size_t* num_ready_handles,
             base::span<Handle> ready_handles,
             base::span<MojoResult> ready_results,
-            MojoHandleSignalsState* signals_states = nullptr);
+            base::span<HandleSignalsState> signals_states = {});
 
  private:
   class State;
diff --git a/net/base/features.cc b/net/base/features.cc
index ea31e4d..844f2f4 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -537,6 +537,12 @@
                    &kDeviceBoundSessionsFederatedRegistration,
                    "CheckWellKnown",
                    true);
+BASE_FEATURE_PARAM(
+    bool,
+    kDeviceBoundSessionFederatedRegistrationRequireThumbprintMatch,
+    &kDeviceBoundSessionsFederatedRegistration,
+    "RequireThumbprintMatch",
+    true);
 
 BASE_FEATURE(kDeviceBoundSessionProactiveRefresh,
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/net/base/features.h b/net/base/features.h
index d1767b0..a3a1413 100644
--- a/net/base/features.h
+++ b/net/base/features.h
@@ -647,6 +647,10 @@
 NET_EXPORT BASE_DECLARE_FEATURE_PARAM(
     bool,
     kDeviceBoundSessionsFederatedRegistrationCheckWellKnown);
+// Controls whether federated sessions require the key thumbprint to match.
+NET_EXPORT BASE_DECLARE_FEATURE_PARAM(
+    bool,
+    kDeviceBoundSessionFederatedRegistrationRequireThumbprintMatch);
 
 // This feature controls whether to proactively trigger Device
 // Bound Session refreshes when a cookie is soon to expire.
diff --git a/net/base/network_change_notifier_win.cc b/net/base/network_change_notifier_win.cc
index 22750a86..662cffa3 100644
--- a/net/base/network_change_notifier_win.cc
+++ b/net/base/network_change_notifier_win.cc
@@ -35,6 +35,15 @@
 // Time between NotifyAddrChange retries, on failure.
 const int kWatchForAddressChangeRetryIntervalMs = 500;
 
+decltype(&GetNetworkConnectivityHint) GetGetNetworkConnectivityHint() {
+  HMODULE hmod = LoadLibraryW(L"IPHLPAPI.DLL");
+  CHECK(hmod);
+  // GetNetworkConnectivityHint is not present on Windows < 19041 so allow
+  // this to return nullptr on failure to lookup.
+  return reinterpret_cast<decltype(&GetNetworkConnectivityHint)>(
+      GetProcAddress(hmod, "GetNetworkConnectivityHint"));
+}
+
 }  // namespace
 
 NetworkChangeNotifierWin::NetworkChangeNotifierWin()
@@ -80,20 +89,16 @@
 // static
 NetworkChangeNotifier::ConnectionType
 NetworkChangeNotifierWin::RecomputeCurrentConnectionTypeModern() {
-  using GetNetworkConnectivityHintType =
-      decltype(&::GetNetworkConnectivityHint);
-
   // This API is only available on Windows 10 Build 19041. However, it works
-  // inside the Network Service Sandbox, so is preferred. See
-  GetNetworkConnectivityHintType get_network_connectivity_hint =
-      reinterpret_cast<GetNetworkConnectivityHintType>(::GetProcAddress(
-          ::GetModuleHandleA("iphlpapi.dll"), "GetNetworkConnectivityHint"));
-  if (!get_network_connectivity_hint) {
+  // inside the Network Service Sandbox, so is preferred.
+  static decltype(&GetNetworkConnectivityHint)
+      get_network_connectivity_hint_fn = GetGetNetworkConnectivityHint();
+  if (!get_network_connectivity_hint_fn) {
     return NetworkChangeNotifier::CONNECTION_UNKNOWN;
   }
   NL_NETWORK_CONNECTIVITY_HINT hint;
   // https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getnetworkconnectivityhint.
-  auto ret = get_network_connectivity_hint(&hint);
+  auto ret = get_network_connectivity_hint_fn(&hint);
   if (ret != NO_ERROR) {
     return NetworkChangeNotifier::CONNECTION_UNKNOWN;
   }
diff --git a/net/device_bound_sessions/session_service_impl.cc b/net/device_bound_sessions/session_service_impl.cc
index 687493b..9588334 100644
--- a/net/device_bound_sessions/session_service_impl.cc
+++ b/net/device_bound_sessions/session_service_impl.cc
@@ -361,7 +361,9 @@
   }
 
   std::string thumbprint = CreateJwkThumbprint(*algorithm, *pub_key);
-  if (thumbprint != provider_key_thumbprint) {
+  if (features::kDeviceBoundSessionFederatedRegistrationRequireThumbprintMatch
+          .Get() &&
+      thumbprint != provider_key_thumbprint) {
     std::move(callback).Run(base::unexpected(
         SessionError(SessionError::kFederatedKeyThumbprintMismatch)));
     return;
diff --git a/net/device_bound_sessions/session_service_impl_unittest.cc b/net/device_bound_sessions/session_service_impl_unittest.cc
index 2f3725e..cc203eb 100644
--- a/net/device_bound_sessions/session_service_impl_unittest.cc
+++ b/net/device_bound_sessions/session_service_impl_unittest.cc
@@ -232,6 +232,19 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
+class SessionServiceImplTestWithThumbprintMismatchAllowed
+    : public SessionServiceImplTest {
+ public:
+  SessionServiceImplTestWithThumbprintMismatchAllowed() {
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        net::features::kDeviceBoundSessionsFederatedRegistration,
+        {{"RequireThumbprintMatch", "false"}});
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
 TEST_F(SessionServiceImplTest, RegisterSuccess) {
   AddSessionsForTesting({{kSessionId, kRefreshUrlString, kOrigin}});
 
@@ -1292,6 +1305,44 @@
   EXPECT_EQ(relying_session, nullptr);
 }
 
+TEST_F(SessionServiceImplTestWithThumbprintMismatchAllowed,
+       FederatedRegistrationWrongKey) {
+  // Create the provider session
+  SchemefulSite site(kTestUrl);
+  AddSessionsForTesting({{kSessionId, kRefreshUrlString, kOrigin}});
+  Session* provider_session =
+      service().GetSession({site, Session::Id(kSessionId)});
+  ASSERT_NE(provider_session, nullptr);
+
+  // Create the provider key and the correct thumbprint
+  base::test::TestFuture<
+      unexportable_keys::ServiceErrorOr<unexportable_keys::UnexportableKeyId>>
+      key_future;
+  key_service()->GenerateSigningKeySlowlyAsync(
+      {crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256},
+      unexportable_keys::BackgroundTaskPriority::kBestEffort,
+      key_future.GetCallback());
+  unexportable_keys::UnexportableKeyId key = *key_future.Take();
+  provider_session->set_unexportable_key_id(key);
+
+  // Attempt a registration with a session provider
+  auto scoped_test_fetcher = ScopedTestRegistrationFetcher::CreateWithSuccess(
+      "RelyingSession", "https://rp.com/refresh", "https://rp.com");
+  auto fetch_param = RegistrationFetcherParam::CreateInstanceForTesting(
+      kTestUrl, {crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256},
+      "challenge", /*authorization=*/std::nullopt, "not_the_thumbprint",
+      kTestRefreshUrl, Session::Id(kSessionId));
+  service().RegisterBoundSession(
+      SessionService::OnAccessCallback(), std::move(fetch_param),
+      IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
+      NetLogWithSource(), /*original_request_initiator=*/std::nullopt);
+
+  // Validate the relying session exists.
+  Session* relying_session = service().GetSession(
+      {SchemefulSite(GURL("https://rp.com")), Session::Id("RelyingSession")});
+  EXPECT_NE(relying_session, nullptr);
+}
+
 TEST_F(SessionServiceImplTestWithFederatedSessions,
        FederatedRegistrationWrongSession) {
   // Create the provider session
diff --git a/net/dns/address_sorter_posix.cc b/net/dns/address_sorter_posix.cc
index 36d5cd9f9..c2206ae 100644
--- a/net/dns/address_sorter_posix.cc
+++ b/net/dns/address_sorter_posix.cc
@@ -59,17 +59,10 @@
   return p1.prefix_length > p2.prefix_length;
 }
 
-// Creates sorted PolicyTable from |table| with |size| entries.
-// TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
-// redundant in M143.
+// Creates sorted PolicyTable from |table|.
 AddressSorterPosix::PolicyTable LoadPolicy(
-    base::span<const AddressSorterPosix::PolicyEntry> table,
-    size_t spanification_suspected_redundant_size) {
-  CHECK(spanification_suspected_redundant_size == table.size(),
-        base::NotFatalUntil::M143);
-  AddressSorterPosix::PolicyTable result(
-      table.data(),
-      table.subspan(spanification_suspected_redundant_size).data());
+    base::span<const AddressSorterPosix::PolicyEntry> table) {
+  AddressSorterPosix::PolicyTable result(table.begin(), table.end());
   std::sort(result.begin(), result.end(), ComparePolicy);
   return result;
 }
@@ -353,12 +346,9 @@
 
 AddressSorterPosix::AddressSorterPosix(ClientSocketFactory* socket_factory)
     : socket_factory_(socket_factory),
-      precedence_table_(LoadPolicy(kDefaultPrecedenceTable,
-                                   std::size(kDefaultPrecedenceTable))),
-      label_table_(
-          LoadPolicy(kDefaultLabelTable, std::size(kDefaultLabelTable))),
-      ipv4_scope_table_(LoadPolicy(kDefaultIPv4ScopeTable,
-                                   std::size(kDefaultIPv4ScopeTable))) {
+      precedence_table_(LoadPolicy(kDefaultPrecedenceTable)),
+      label_table_(LoadPolicy(kDefaultLabelTable)),
+      ipv4_scope_table_(LoadPolicy(kDefaultIPv4ScopeTable)) {
   NetworkChangeNotifier::AddIPAddressObserver(this);
   OnIPAddressChanged(NetworkChangeNotifier::IP_ADDRESS_CHANGE_NORMAL);
 }
diff --git a/net/proxy_resolution/configured_proxy_resolution_request.cc b/net/proxy_resolution/configured_proxy_resolution_request.cc
index cb8541d0..90b826e 100644
--- a/net/proxy_resolution/configured_proxy_resolution_request.cc
+++ b/net/proxy_resolution/configured_proxy_resolution_request.cc
@@ -67,7 +67,7 @@
     CHECK(host_resolver);
 
     for (const auto& rule : service_->config_->value().proxy_override_rules()) {
-      if (rule.destination_matchers.Matches(url_)) {
+      if (rule.MatchesDestination(url_)) {
         // TODO(crbug.com/454638342): Add optimization to skip marking a rule as
         // applicable if we can already discard it based on cached DNS
         // resolution state (and avoid starting actual DNS resolution that is
@@ -342,7 +342,7 @@
   switch (dns_condition.result) {
     case ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::kNotFound:
       return dns_result.is_address_list_empty;
-    case ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::kResolves:
+    case ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::kResolved:
       return !dns_result.is_address_list_empty;
   }
 }
diff --git a/net/proxy_resolution/configured_proxy_resolution_service.cc b/net/proxy_resolution/configured_proxy_resolution_service.cc
index 3ed7c1d..8c0fedf 100644
--- a/net/proxy_resolution/configured_proxy_resolution_service.cc
+++ b/net/proxy_resolution/configured_proxy_resolution_service.cc
@@ -1045,7 +1045,7 @@
       !config_->value().proxy_override_rules().empty()) {
     // Override rules are prioritized in the order that they are defined.
     for (const auto& rule : config_->value().proxy_override_rules()) {
-      if (rule.destination_matchers.Matches(url)) {
+      if (rule.MatchesDestination(url)) {
         if (rule.dns_conditions.empty()) {
           net_log.AddEvent(
               NetLogEventType::PROXY_RESOLUTION_OVERRIDE_RULE_APPLIED,
diff --git a/net/proxy_resolution/configured_proxy_resolution_service_unittest.cc b/net/proxy_resolution/configured_proxy_resolution_service_unittest.cc
index 982a6cf3..610c656 100644
--- a/net/proxy_resolution/configured_proxy_resolution_service_unittest.cc
+++ b/net/proxy_resolution/configured_proxy_resolution_service_unittest.cc
@@ -450,7 +450,8 @@
     const ProxyList& proxy_list,
     std::optional<std::string_view> dns_host = std::nullopt,
     ProxyOverrideRule::DnsProbeCondition::Result dns_condition_result =
-        ProxyOverrideRule::DnsProbeCondition::Result::kResolves) {
+        ProxyOverrideRule::DnsProbeCondition::Result::kResolved,
+    std::string_view exclude_destination_matcher = "") {
   ProxyConfig::ProxyOverrideRule override_rule;
   override_rule.destination_matchers.AddRuleFromString(destination_matcher);
   override_rule.proxy_list = proxy_list;
@@ -458,6 +459,10 @@
     override_rule.dns_conditions.emplace_back(
         url::SchemeHostPort(GURL(dns_host.value())), dns_condition_result);
   }
+  if (!exclude_destination_matcher.empty()) {
+    override_rule.exclude_destination_matchers.AddRuleFromString(
+        exclude_destination_matcher);
+  }
   return override_rule;
 }
 
@@ -4864,6 +4869,80 @@
                                   NetLogEventType::PROXY_RESOLUTION_SERVICE));
 }
 
+TEST_F(ConfiguredProxyResolutionServiceTest, ExcludedOverrideRuleWithPac) {
+  auto config = ProxyConfig::CreateAutoDetect();
+  auto override_rule = CreateOverrideRule(
+      kMatchingRule, CreateProxyList(kProxy1), /*dns_host=*/std::nullopt,
+      ProxyOverrideRule::DnsProbeCondition::Result::kResolved,
+      /*exclude_destination_matcher=*/"*.test");
+  config.set_proxy_override_rules({override_rule});
+
+  mock_host_resolver_ = std::make_unique<MockCachingHostResolver>();
+  auto config_service =
+      std::make_unique<MockProxyConfigService>(std::move(config));
+  MockAsyncProxyResolver resolver;
+  auto factory = std::make_unique<MockAsyncProxyResolverFactory>(true);
+  auto* factory_ptr = factory.get();
+  ConfiguredProxyResolutionService service(
+      std::move(config_service), std::move(factory), mock_host_resolver_.get(),
+      /*net_log=*/nullptr,
+      /*quick_check_enabled=*/true);
+
+  auto fetcher = std::make_unique<MockPacFileFetcher>();
+  auto* fetcher_ptr = fetcher.get();
+  service.SetPacFileFetchers(std::move(fetcher),
+                             std::make_unique<DoNothingDhcpPacFileFetcher>());
+
+  RecordingNetLogObserver net_log_observer;
+
+  ProxyInfo info;
+  TestCompletionCallback callback;
+  std::unique_ptr<ProxyResolutionRequest> request;
+  int rv = service.ResolveProxy(
+      GURL(kMatchingUrl), std::string(), NetworkAnonymizationKey(), &info,
+      callback.callback(), &request,
+      NetLogWithSource::Make(NetLogSourceType::NONE), DEFAULT_PRIORITY);
+  EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
+  EXPECT_TRUE(request);
+  ASSERT_FALSE(callback.have_result());
+
+  EXPECT_TRUE(fetcher_ptr->has_pending_request());
+  EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher_ptr->pending_request_url());
+  fetcher_ptr->NotifyFetchCompletion(OK, kValidPacScript1);
+
+  EXPECT_EQ(kValidPacScript116,
+            factory_ptr->pending_requests()[0]->script_data()->utf16());
+  factory_ptr->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver);
+
+  GURL sanitized_matching_url(kSanitizedMatchingUrl);
+  JobMap jobs = GetPendingJobsForURLs(resolver, sanitized_matching_url);
+  jobs[sanitized_matching_url]->results()->UseNamedProxy("request1:80");
+  jobs[sanitized_matching_url]->CompleteNow(OK);
+
+  EXPECT_THAT(callback.WaitForResult(), IsOk());
+  EXPECT_EQ(info.proxy_chain().ToDebugString(), "[request1:80]");
+
+  const auto& entries = net_log_observer.GetEntries();
+  EXPECT_EQ(entries.size(), 7U);
+  EXPECT_TRUE(LogContainsBeginEvent(entries, 0,
+                                    NetLogEventType::PROXY_RESOLUTION_SERVICE));
+  EXPECT_TRUE(LogContainsBeginEvent(
+      entries, 1,
+      NetLogEventType::PROXY_RESOLUTION_SERVICE_WAITING_FOR_INIT_PAC));
+  EXPECT_TRUE(LogContainsEndEvent(
+      entries, 2,
+      NetLogEventType::PROXY_RESOLUTION_SERVICE_WAITING_FOR_INIT_PAC));
+  EXPECT_TRUE(LogContainsBeginEvent(
+      entries, 3, NetLogEventType::PROXY_RESOLUTION_OVERRIDE_RULES));
+  EXPECT_TRUE(LogContainsEndEvent(
+      entries, 4, NetLogEventType::PROXY_RESOLUTION_OVERRIDE_RULES));
+  EXPECT_TRUE(LogContainsEvent(
+      entries, 5, NetLogEventType::PROXY_RESOLUTION_SERVICE_RESOLVED_PROXY_LIST,
+      NetLogEventPhase::NONE));
+  EXPECT_TRUE(LogContainsEndEvent(entries, 6,
+                                  NetLogEventType::PROXY_RESOLUTION_SERVICE));
+}
+
 TEST_F(ConfiguredProxyResolutionServiceTest,
        OverrideRuleWithoutHostAppliedSync) {
   auto proxy_list = CreateProxyList(kProxy1);
@@ -5808,14 +5887,14 @@
   auto override_rule = CreateOverrideRule(
       kMatchingRule, proxy_list, kDnsHost1,
       params.first_condition_resolves
-          ? ProxyOverrideRule::DnsProbeCondition::Result::kResolves
+          ? ProxyOverrideRule::DnsProbeCondition::Result::kResolved
           : ProxyOverrideRule::DnsProbeCondition::Result::kNotFound);
   override_rule.dns_conditions.push_back(
       ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
           .host = url::SchemeHostPort(GURL(kDnsHost2)),
           .result =
               params.second_condition_resolves
-                  ? ProxyOverrideRule::DnsProbeCondition::Result::kResolves
+                  ? ProxyOverrideRule::DnsProbeCondition::Result::kResolved
                   : ProxyOverrideRule::DnsProbeCondition::Result::kNotFound});
   config.set_proxy_override_rules({std::move(override_rule)});
 
@@ -6065,7 +6144,7 @@
     override_rule1.dns_conditions.emplace_back(
         url::SchemeHostPort(GURL(kDnsHost1)),
         params.first_dns_host_resolves
-            ? ProxyOverrideRule::DnsProbeCondition::Result::kResolves
+            ? ProxyOverrideRule::DnsProbeCondition::Result::kResolved
             : ProxyOverrideRule::DnsProbeCondition::Result::kNotFound);
   }
 
@@ -6077,7 +6156,7 @@
     override_rule2.dns_conditions.emplace_back(
         url::SchemeHostPort(GURL(kDnsHost2)),
         params.second_dns_host_resolves
-            ? ProxyOverrideRule::DnsProbeCondition::Result::kResolves
+            ? ProxyOverrideRule::DnsProbeCondition::Result::kResolved
             : ProxyOverrideRule::DnsProbeCondition::Result::kNotFound);
   }
 
diff --git a/net/proxy_resolution/proxy_config.cc b/net/proxy_resolution/proxy_config.cc
index 230c74a..fb3ae94 100644
--- a/net/proxy_resolution/proxy_config.cc
+++ b/net/proxy_resolution/proxy_config.cc
@@ -255,6 +255,7 @@
 bool ProxyConfig::ProxyOverrideRule::operator==(
     const ProxyOverrideRule& other) const {
   return destination_matchers == other.destination_matchers &&
+         exclude_destination_matchers == other.exclude_destination_matchers &&
          dns_conditions == other.dns_conditions &&
          proxy_list.Equals(other.proxy_list);
 }
@@ -273,6 +274,11 @@
   return dict;
 }
 
+bool ProxyConfig::ProxyOverrideRule::MatchesDestination(const GURL& url) const {
+  return destination_matchers.Matches(url) &&
+         !exclude_destination_matchers.Matches(url);
+}
+
 bool ProxyConfig::ProxyOverrideRule::DnsProbeCondition::operator==(
     const DnsProbeCondition& other) const = default;
 
@@ -281,13 +287,13 @@
   base::Value::Dict dict;
   dict.Set("host", host.Serialize());
 
-  std::string result_str;
+  std::string_view result_str;
   switch (result) {
     case DnsProbeCondition::Result::kNotFound:
       result_str = "NotFound";
       break;
-    case DnsProbeCondition::Result::kResolves:
-      result_str = "Resolves";
+    case DnsProbeCondition::Result::kResolved:
+      result_str = "Resolved";
       break;
   }
   dict.Set("result", result_str);
diff --git a/net/proxy_resolution/proxy_config.h b/net/proxy_resolution/proxy_config.h
index e416939..6651494 100644
--- a/net/proxy_resolution/proxy_config.h
+++ b/net/proxy_resolution/proxy_config.h
@@ -173,7 +173,7 @@
     // be used. Each condition includes a `host` to try to resolve, and the rule
     // only applies if host resolution matches the expected `result`.
     struct NET_EXPORT DnsProbeCondition {
-      enum Result { kNotFound, kResolves };
+      enum Result { kNotFound, kResolved };
 
       bool operator==(const DnsProbeCondition& other) const;
 
@@ -197,7 +197,14 @@
     // Creates a Value::Dict dump of this override rule.
     base::Value::Dict ToDict() const;
 
+    // Returns true if `url` matches `destination_matchers` without matching
+    // `exclude_destination_matchers`. This should be used instead of directly
+    // accessing the matcher members for evaluating if the rule is applicable or
+    // not.
+    bool MatchesDestination(const GURL& url) const;
+
     ProxyHostMatchingRules destination_matchers;
+    ProxyHostMatchingRules exclude_destination_matchers;
     std::vector<DnsProbeCondition> dns_conditions;
 
     ProxyList proxy_list;
diff --git a/net/proxy_resolution/proxy_config_unittest.cc b/net/proxy_resolution/proxy_config_unittest.cc
index 11f1b49..d6a2957 100644
--- a/net/proxy_resolution/proxy_config_unittest.cc
+++ b/net/proxy_resolution/proxy_config_unittest.cc
@@ -107,81 +107,91 @@
   kHostMatches,
   kResultMatches,
   kNoConditions,
+  kNoExcludeDestinationMatchers,
 };
 
-class ProxyConfigOverrideRulesTest
-    : public testing::TestWithParam<testing::tuple<bool, bool, TestCondition>> {
- public:
-  ProxyConfig::ProxyOverrideRule CreateOverrideRule(
-      bool include_matchers,
-      bool include_proxy_list,
-      TestCondition test_condition) {
-    ProxyConfig::ProxyOverrideRule rule;
+ProxyConfig::ProxyOverrideRule CreateOverrideRule(
+    bool include_matchers,
+    bool include_proxy_list,
+    TestCondition test_condition) {
+  ProxyConfig::ProxyOverrideRule rule;
 
-    if (include_matchers) {
-      rule.destination_matchers.AddRuleFromString("192.168.1.1");
-      rule.destination_matchers.AddRuleFromString(
-          "[3ffe:2a00:100:7031:0:0::1]");
-      rule.destination_matchers.AddRuleFromString("*.org:443");
-      rule.destination_matchers.AddRuleFromString("www.google.com");
-      rule.destination_matchers.AddRuleFromString("http://www.google.com");
-    }
+  if (include_matchers) {
+    rule.destination_matchers.AddRuleFromString("192.168.1.1");
+    rule.destination_matchers.AddRuleFromString("[3ffe:2a00:100:7031:0:0::1]");
+    rule.destination_matchers.AddRuleFromString("*.org:443");
+    rule.destination_matchers.AddRuleFromString("*google.com");
 
-    if (include_proxy_list) {
-      rule.proxy_list.SetFromPacString("HTTPS foo:333; DIRECT");
-    }
-
-    auto condition = ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
-        .host = url::SchemeHostPort("http", "ads.corps", 321),
-        .result = ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::
-            kNotFound,
-    };
-    // Only one condition is changed in the non `kDefault` cases to validate the
-    // entire array is evaluated for equality.
-    switch (test_condition) {
-      case TestCondition::kDefault:
-        break;
-      case TestCondition::kHostMatches:
-        condition.result = ProxyConfig::ProxyOverrideRule::DnsProbeCondition::
-            Result::kResolves;
-        break;
-      case TestCondition::kResultMatches:
-        condition.host = url::SchemeHostPort("http", "other.corps", 321);
-        break;
-      case TestCondition::kNoConditions:
-        return rule;
-    }
-    rule.dns_conditions = {
-        ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
-            .host = url::SchemeHostPort("https", "corp.ads", 123),
-            .result = ProxyConfig::ProxyOverrideRule::DnsProbeCondition::
-                Result::kResolves,
-        },
-        condition};
-
-    return rule;
+    rule.exclude_destination_matchers.AddRuleFromString("mail.google.com");
+    rule.exclude_destination_matchers.AddRuleFromString("http://*.org");
   }
+
+  if (include_proxy_list) {
+    rule.proxy_list.SetFromPacString("HTTPS foo:333; DIRECT");
+  }
+
+  auto condition = ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
+      .host = url::SchemeHostPort("http", "ads.corps", 321),
+      .result =
+          ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::kNotFound,
+  };
+
+  // Only one condition is changed in the non `kDefault` cases to validate the
+  // entire array is evaluated for equality.
+  switch (test_condition) {
+    case TestCondition::kDefault:
+      break;
+    case TestCondition::kHostMatches:
+      condition.result =
+          ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::kResolved;
+      break;
+    case TestCondition::kResultMatches:
+      condition.host = url::SchemeHostPort("http", "other.corps", 321);
+      break;
+    case TestCondition::kNoConditions:
+      return rule;
+    case TestCondition::kNoExcludeDestinationMatchers:
+      rule.exclude_destination_matchers.Clear();
+      break;
+  }
+  rule.dns_conditions = {
+      ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
+          .host = url::SchemeHostPort("https", "corp.ads", 123),
+          .result = ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::
+              kResolved,
+      },
+      condition};
+
+  return rule;
+}
+
+class ProxyConfigOverrideRulesEqualityTest
+    : public testing::TestWithParam<testing::tuple<bool, bool, TestCondition>> {
 };
 
 INSTANTIATE_TEST_SUITE_P(
     All,
-    ProxyConfigOverrideRulesTest,
-    testing::Combine(testing::Bool(),
-                     testing::Bool(),
-                     testing::Values(TestCondition::kDefault,
-                                     TestCondition::kHostMatches,
-                                     TestCondition::kResultMatches,
-                                     TestCondition::kNoConditions)));
+    ProxyConfigOverrideRulesEqualityTest,
+    testing::Combine(
+        testing::Bool(),
+        testing::Bool(),
+        testing::Values(TestCondition::kDefault,
+                        TestCondition::kHostMatches,
+                        TestCondition::kResultMatches,
+                        TestCondition::kNoConditions,
+                        TestCondition::kNoExcludeDestinationMatchers)));
 
-TEST_P(ProxyConfigOverrideRulesTest, Equals) {
+TEST_P(ProxyConfigOverrideRulesEqualityTest, Equals) {
   ProxyConfig config1;
   ProxyConfig config2;
 
-  config1.set_proxy_override_rules(
-      {CreateOverrideRule(true, true, TestCondition::kDefault)});
+  config1.set_proxy_override_rules({CreateOverrideRule(
+      /*include_matchers=*/true, /*include_proxy_list=*/true,
+      TestCondition::kDefault)});
   config2.set_proxy_override_rules(
-      {CreateOverrideRule(std::get<0>(GetParam()), std::get<1>(GetParam()),
-                          std::get<2>(GetParam()))});
+      {CreateOverrideRule(/*include_matchers=*/std::get<0>(GetParam()),
+                          /*include_proxy_list=*/std::get<1>(GetParam()),
+                          /*test_condition=*/std::get<2>(GetParam()))});
 
   if (std::get<0>(GetParam()) && std::get<1>(GetParam()) &&
       std::get<2>(GetParam()) == TestCondition::kDefault) {
@@ -193,6 +203,45 @@
   }
 }
 
+TEST(ProxyConfigTest, OverrideRulesMatchesDestination) {
+  ProxyConfig config;
+
+  config.set_proxy_override_rules({CreateOverrideRule(
+      /*include_matchers=*/true, /*include_proxy_list=*/true,
+      TestCondition::kDefault)});
+  const auto& rule = config.proxy_override_rules().at(0);
+
+  // Rules are set to match the following URLs:
+  // - 192.168.1.1
+  // - [3ffe:2a00:100:7031:0:0::1]
+  // - *.org:443
+  // - *google.com
+  EXPECT_TRUE(rule.MatchesDestination(GURL("http://192.168.1.1")));
+  EXPECT_TRUE(rule.MatchesDestination(GURL("https://192.168.1.1")));
+  EXPECT_TRUE(
+      rule.MatchesDestination(GURL("http://[3ffe:2a00:100:7031:0:0::1]")));
+  EXPECT_TRUE(
+      rule.MatchesDestination(GURL("https://[3ffe:2a00:100:7031:0:0::1]")));
+  EXPECT_TRUE(rule.MatchesDestination(GURL("http://google.com")));
+  EXPECT_TRUE(rule.MatchesDestination(GURL("https://google.com")));
+  EXPECT_TRUE(rule.MatchesDestination(GURL("https://calendar.google.com")));
+  EXPECT_TRUE(rule.MatchesDestination(GURL("https://google.org:443")));
+
+  EXPECT_FALSE(rule.MatchesDestination(GURL("http://192.168.1.2")));
+  EXPECT_FALSE(rule.MatchesDestination(GURL("https://192.168.1.2")));
+  EXPECT_FALSE(rule.MatchesDestination(GURL("[3ffe:ffff:100:7031:0:0::1]")));
+  EXPECT_FALSE(rule.MatchesDestination(GURL("http://gooogle.com")));
+  EXPECT_FALSE(rule.MatchesDestination(GURL("https://google.net")));
+  EXPECT_FALSE(rule.MatchesDestination(GURL("https://google.org:123")));
+
+  // The following patterns are exceptions:
+  // - mail.google.com
+  // - http://*.org
+  EXPECT_FALSE(rule.MatchesDestination(GURL("http://mail.google.com")));
+  EXPECT_FALSE(rule.MatchesDestination(GURL("https://mail.google.com")));
+  EXPECT_FALSE(rule.MatchesDestination(GURL("http://google.org:443")));
+}
+
 #if BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)
 TEST(ProxyConfigTest, EqualsMultiProxyChains) {
   ProxyConfig config1;
@@ -365,7 +414,7 @@
       ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
           .host = url::SchemeHostPort("https", "ads2.corps", 443),
           .result = ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::
-              kResolves},
+              kResolved},
   };
 
   auto config = ProxyConfig::CreateDirect();
@@ -374,7 +423,7 @@
           "{\"override_rules\":[{\"destination_matchers\":\"http://"
           "www.example.com;\",\"dns_conditions\":[{\"host\":\"http://"
           "ads.corps:321\",\"result\":\"NotFound\"},{\"host\":\"https://"
-          "ads2.corps\",\"result\":\"Resolves\"}],\"proxy_list\":[\"[https://"
+          "ads2.corps\",\"result\":\"Resolved\"}],\"proxy_list\":[\"[https://"
           "foo:333]\",\"direct://\"]}]}"};
 }
 
diff --git a/sandbox/policy/win/sandbox_win.cc b/sandbox/policy/win/sandbox_win.cc
index 6dde72c..dac5e25 100644
--- a/sandbox/policy/win/sandbox_win.cc
+++ b/sandbox/policy/win/sandbox_win.cc
@@ -830,17 +830,12 @@
 
   void BeforeTargetProcessCreateOnCreationThread(
       const void* trace_id) override {
-    int active_threads = ++creation_threads_in_use_;
-    base::UmaHistogramCounts100("MPArch.ChildProcessLaunchActivelyInParallel",
-                                active_threads);
-
     TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("startup", "TargetProcess::Create",
                                       trace_id);
   }
 
   void AfterTargetProcessCreateOnCreationThread(const void* trace_id,
                                                 DWORD process_id) override {
-    creation_threads_in_use_--;
     TRACE_EVENT_NESTABLE_ASYNC_END1("startup", "TargetProcess::Create",
                                     trace_id, "pid", process_id);
   }
@@ -854,12 +849,6 @@
     UMA_HISTOGRAM_SPARSE("Process.Sandbox.IPC.ThreadDuplicateHandleErrorCode",
                          last_error);
   }
-
- private:
-  // When parallel launching is enabled, target creation will happen on the
-  // thread pool. This is atomic to keep track of the number of threads that are
-  // currently creating processes.
-  std::atomic<int> creation_threads_in_use_ = 0;
 };
 
 // static
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
index 031f31a2..4b05700 100644
--- a/services/network/network_service.cc
+++ b/services/network/network_service.cc
@@ -43,7 +43,6 @@
 #include "build/chromecast_buildflags.h"
 #include "components/ip_protection/common/ip_protection_telemetry.h"
 #include "components/ip_protection/common/masked_domain_list_manager.h"
-#include "components/network_session_configurator/common/network_features.h"
 #include "components/os_crypt/sync/os_crypt.h"
 #include "components/privacy_sandbox/masked_domain_list/masked_domain_list.pb.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
diff --git a/services/network/public/cpp/permissions_policy/permissions_policy_features.json5 b/services/network/public/cpp/permissions_policy/permissions_policy_features.json5
index 99803cab..a50dce49 100644
--- a/services/network/public/cpp/permissions_policy/permissions_policy_features.json5
+++ b/services/network/public/cpp/permissions_policy/permissions_policy_features.json5
@@ -424,6 +424,11 @@
       permissions_policy_name: "magnetometer",
     },
     {
+      name: "ManualText",
+      permissions_policy_name: "manual-text",
+      depends_on: ["ManualText"],
+    },
+    {
       name: "MediaPlaybackWhileNotVisible",
       permissions_policy_name: "media-playback-while-not-visible",
       feature_default: "EnableForAll",
diff --git a/services/network/public/cpp/proxy_config_mojom_traits.cc b/services/network/public/cpp/proxy_config_mojom_traits.cc
index fbea938..8a572af 100644
--- a/services/network/public/cpp/proxy_config_mojom_traits.cc
+++ b/services/network/public/cpp/proxy_config_mojom_traits.cc
@@ -95,8 +95,8 @@
   switch (result) {
     case net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kNotFound:
       return network::mojom::ProxyOverrideRuleResult::kNotFound;
-    case net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kResolves:
-      return network::mojom::ProxyOverrideRuleResult::kResolves;
+    case net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::kResolved:
+      return network::mojom::ProxyOverrideRuleResult::kResolved;
   }
 }
 
@@ -111,9 +111,9 @@
       *out = net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::
           kNotFound;
       return true;
-    case network::mojom::ProxyOverrideRuleResult::kResolves:
+    case network::mojom::ProxyOverrideRuleResult::kResolved:
       *out = net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::
-          kResolves;
+          kResolved;
       return true;
   }
   return false;
@@ -145,6 +145,8 @@
     Read(network::mojom::ProxyOverrideRuleDataView data,
          net::ProxyConfig::ProxyOverrideRule* out) {
   return data.ReadDestinationMatchers(&out->destination_matchers) &&
+         data.ReadExcludeDestinationMatchers(
+             &out->exclude_destination_matchers) &&
          data.ReadProxyList(&out->proxy_list) &&
          data.ReadDnsConditions(&out->dns_conditions) &&
          !out->destination_matchers.rules().empty() &&
diff --git a/services/network/public/cpp/proxy_config_mojom_traits.h b/services/network/public/cpp/proxy_config_mojom_traits.h
index 21a301b0..b052088 100644
--- a/services/network/public/cpp/proxy_config_mojom_traits.h
+++ b/services/network/public/cpp/proxy_config_mojom_traits.h
@@ -135,6 +135,10 @@
       const net::ProxyConfig::ProxyOverrideRule& r) {
     return r.destination_matchers;
   }
+  static net::ProxyHostMatchingRules exclude_destination_matchers(
+      const net::ProxyConfig::ProxyOverrideRule& r) {
+    return r.exclude_destination_matchers;
+  }
   static const std::vector<
       net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition>&
   dns_conditions(const net::ProxyConfig::ProxyOverrideRule& r) {
diff --git a/services/network/public/cpp/proxy_config_mojom_traits_unittest.cc b/services/network/public/cpp/proxy_config_mojom_traits_unittest.cc
index d4d2a2fb..e998536 100644
--- a/services/network/public/cpp/proxy_config_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/proxy_config_mojom_traits_unittest.cc
@@ -148,13 +148,17 @@
   rule.destination_matchers.AddRuleFromString("www.google.com");
   rule.destination_matchers.AddRuleFromString("http://www.google.com");
 
+  rule.exclude_destination_matchers.AddRuleFromString("*.org:123");
+  rule.exclude_destination_matchers.AddRuleFromString("www.mailgoogle.com");
+  rule.exclude_destination_matchers.AddRuleFromString("http://mail.google.com");
+
   rule.proxy_list.SetFromPacString("HTTPS foo:333; DIRECT");
 
   rule.dns_conditions = {
       net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
           .host = url::SchemeHostPort("https", "corp.ads", 123),
           .result = net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::
-              Result::kResolves,
+              Result::kResolved,
       },
       net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
           .host = url::SchemeHostPort("https", "ads.corps", 321),
@@ -179,7 +183,7 @@
       net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
           .host = url::SchemeHostPort("https", "corp.ads", 123),
           .result = net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::
-              Result::kResolves,
+              Result::kResolved,
       },
       net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
           .host = url::SchemeHostPort("https", "ads.corps", 321),
@@ -211,7 +215,7 @@
       net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
           .host = url::SchemeHostPort("https", "corp.ads", 123),
           .result = net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition::
-              Result::kResolves,
+              Result::kResolved,
       },
       net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition{
           .host = url::SchemeHostPort("https", "ads.corps", 321),
diff --git a/services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom b/services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom
index 9b64bd19..4c3e6ec 100644
--- a/services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom
+++ b/services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom
@@ -342,6 +342,10 @@
   // See https://github.com/WICG/direct-sockets/blob/main/docs/multicast-explainer.md.
   kMulticastInDirectSockets = 142,
 
+  // Controls whether Autofill may fill fields in that frame when triggered on
+  // a field with the main frame's origin.
+  kManualText = 143,
+
   // Don't change assigned numbers of any item, and don't reuse removed slots.
   // Add new features at the end of the enum.
   // Also, run update_permissions_policy_enum.py in
diff --git a/services/network/public/mojom/proxy_config.mojom b/services/network/public/mojom/proxy_config.mojom
index 44d8180..1047c8d 100644
--- a/services/network/public/mojom/proxy_config.mojom
+++ b/services/network/public/mojom/proxy_config.mojom
@@ -41,7 +41,7 @@
 // This corresponds to `net::ProxyConfig::ProxyOverrideRule::Result`.
 enum ProxyOverrideRuleResult {
   kNotFound,
-  kResolves,
+  kResolved,
 };
 
 // This corresponds to `net::ProxyConfig::ProxyOverrideRule::DnsProbeCondition`.
@@ -53,6 +53,7 @@
 // These fields mirror those of `net::ProxyConfig::ProxyOverrideRule`.
 struct ProxyOverrideRule {
   ProxyHostMatchingRules destination_matchers;
+  ProxyHostMatchingRules exclude_destination_matchers;
   array<DnsProbeCondition> dns_conditions;
 
   ProxyList proxy_list;
diff --git a/services/webnn/webnn_graph_impl.cc b/services/webnn/webnn_graph_impl.cc
index 09780ff0..9d937ea4 100644
--- a/services/webnn/webnn_graph_impl.cc
+++ b/services/webnn/webnn_graph_impl.cc
@@ -193,14 +193,14 @@
                  name_to_input_tensor_map,
              base::flat_map<std::string, scoped_refptr<WebNNTensorImpl>>
                  name_to_output_tensor_map,
-             ScopedTrace scoped_trace) {
+             ScopedTrace scoped_trace,
+             mojo::ReportBadMessageCallback bad_message_cb) {
             for (auto& [name, tensor] : name_to_input_tensor_map) {
               if (tensor->is_exported()) {
                 LOG(ERROR)
                     << "[WebNN] Invalid to dispatch graph when input tensor (" +
                            name + ") is exported.";
-                self->GetMojoReceiver().ReportBadMessage(
-                    kBadMessageInvalidTensor);
+                std::move(bad_message_cb).Run(kBadMessageInvalidTensor);
                 return;
               }
             }
@@ -210,8 +210,7 @@
                 LOG(ERROR) << "[WebNN] Invalid to dispatch graph when output "
                               "tensor (" +
                                   name + ") is exported.";
-                self->GetMojoReceiver().ReportBadMessage(
-                    kBadMessageInvalidTensor);
+                std::move(bad_message_cb).Run(kBadMessageInvalidTensor);
                 return;
               }
             }
@@ -220,7 +219,8 @@
                                std::move(name_to_output_tensor_map));
           },
           base::RetainedRef(this), std::move(name_to_input_tensor_map),
-          std::move(name_to_output_tensor_map), std::move(scoped_trace)));
+          std::move(name_to_output_tensor_map), std::move(scoped_trace),
+          GetMojoReceiver().GetBadMessageCallback()));
 }
 
 }  // namespace webnn
diff --git a/services/webnn/webnn_tensor_impl.cc b/services/webnn/webnn_tensor_impl.cc
index fb3e6f5..d5da4c9 100644
--- a/services/webnn/webnn_tensor_impl.cc
+++ b/services/webnn/webnn_tensor_impl.cc
@@ -67,20 +67,20 @@
 
   // Call ReadTensorImpl() implemented by a backend.
   context_->scheduler_task_runner()->PostTask(
-      FROM_HERE, base::BindOnce(
-                     [](WebNNTensorImpl* self, ReadTensorCallback callback,
-                        ScopedTrace scoped_trace) {
-                       if (self->is_exported()) {
-                         LOG(ERROR)
-                             << "[WebNN] Invalid to read tensor when exported.";
-                         self->GetMojoReceiver().ReportBadMessage(
-                             kBadMessageInvalidTensor);
-                         return;
-                       }
-                       self->ReadTensorImpl(std::move(callback));
-                     },
-                     base::RetainedRef(this), std::move(mojo_callback_wrapper),
-                     std::move(scoped_trace)));
+      FROM_HERE,
+      base::BindOnce(
+          [](WebNNTensorImpl* self, ReadTensorCallback callback,
+             ScopedTrace scoped_trace,
+             mojo::ReportBadMessageCallback bad_message_cb) {
+            if (self->is_exported()) {
+              LOG(ERROR) << "[WebNN] Invalid to read tensor when exported.";
+              std::move(bad_message_cb).Run(kBadMessageInvalidTensor);
+              return;
+            }
+            self->ReadTensorImpl(std::move(callback));
+          },
+          base::RetainedRef(this), std::move(mojo_callback_wrapper),
+          std::move(scoped_trace), GetMojoReceiver().GetBadMessageCallback()));
 }
 
 void WebNNTensorImpl::WriteTensor(mojo_base::BigBuffer src_buffer) {
@@ -105,17 +105,17 @@
       FROM_HERE,
       base::BindOnce(
           [](WebNNTensorImpl* self, mojo_base::BigBuffer src_buffer,
-             ScopedTrace scoped_trace) {
+             ScopedTrace scoped_trace,
+             mojo::ReportBadMessageCallback bad_message_cb) {
             if (self->is_exported()) {
               LOG(ERROR) << "[WebNN] Invalid to write tensor when exported.";
-              self->GetMojoReceiver().ReportBadMessage(
-                  kBadMessageInvalidTensor);
+              std::move(bad_message_cb).Run(kBadMessageInvalidTensor);
               return;
             }
             self->WriteTensorImpl(std::move(src_buffer));
           },
           base::RetainedRef(this), std::move(src_buffer),
-          std::move(scoped_trace)));
+          std::move(scoped_trace), GetMojoReceiver().GetBadMessageCallback()));
 }
 
 void WebNNTensorImpl::ImportTensor(const gpu::SyncToken& fence) {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 5be05a1..2b72e100 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -8813,6 +8813,24 @@
             ]
         }
     ],
+    "DevToolsConsoleInsightsTeasers": [
+        {
+            "platforms": [
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "DevToolsAiPromptApi"
+                    ]
+                }
+            ]
+        }
+    ],
     "DeviceBoundSessionCredentials2": [
         {
             "platforms": [
@@ -10194,27 +10212,6 @@
             ]
         }
     ],
-    "EnableVerdictCache": [
-        {
-            "platforms": [
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "params": {
-                        "verdict_cache_max_size": "200"
-                    },
-                    "enable_features": [
-                        "EnableVerdictCache"
-                    ]
-                }
-            ]
-        }
-    ],
     "EnableWatermarkCustomization": [
         {
             "platforms": [
@@ -10562,12 +10559,29 @@
             ],
             "experiments": [
                 {
-                    "name": "Variant1",
+                    "name": "Chips_v1",
                     "params": {
-                        "x_iph-variant": "custom-action-iph"
+                        "x_iph-variant": "custom-ui-chip-iph"
                     },
                     "enable_features": [
-                        "ExtensionsCollapseMainMenu",
+                        "IPH_ExtensionsZeroStatePromo"
+                    ]
+                },
+                {
+                    "name": "Chips_v2",
+                    "params": {
+                        "x_iph-variant": "ustom-ui-chip-iph-v2"
+                    },
+                    "enable_features": [
+                        "IPH_ExtensionsZeroStatePromo"
+                    ]
+                },
+                {
+                    "name": "Chips_v3",
+                    "params": {
+                        "x_iph-variant": "custom-ui-chip-iph-v3"
+                    },
+                    "enable_features": [
                         "IPH_ExtensionsZeroStatePromo"
                     ]
                 }
@@ -20536,6 +20550,30 @@
             ]
         }
     ],
+    "ReadAnythingReadAloudTsTextSegmentation": [
+        {
+            "platforms": [
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "ReadAnythingReadAloudTSTextSegmentation"
+                    ]
+                },
+                {
+                    "name": "Disabled",
+                    "disable_features": [
+                        "ReadAnythingReadAloudTSTextSegmentation"
+                    ]
+                }
+            ]
+        }
+    ],
     "ReclaimOldPrepaintTiles": [
         {
             "platforms": [
@@ -23734,24 +23772,6 @@
             ]
         }
     ],
-    "SideBySideKeyboardShortcut": [
-        {
-            "platforms": [
-                "linux",
-                "mac",
-                "windows",
-                "chromeos"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "SideBySideKeyboardShortcut"
-                    ]
-                }
-            ]
-        }
-    ],
     "SidePanelCompanionDesktopM116Plus": [
         {
             "platforms": [
@@ -24109,6 +24129,33 @@
             ]
         }
     ],
+    "SlimScheduler": [
+        {
+            "platforms": [
+                "android",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "pending_frames": "2"
+                    },
+                    "enable_features": [
+                        "DisplaySchedulerAsClient",
+                        "ManualBeginFrame",
+                        "NoCompositorFrameAcks",
+                        "NoLateBeginFrames"
+                    ],
+                    "disable_features": [
+                        "AckOnSurfaceActivationWhenInteractive"
+                    ]
+                }
+            ]
+        }
+    ],
     "SlopBucket": [
         {
             "platforms": [
@@ -25906,6 +25953,25 @@
             ]
         }
     ],
+    "UseCecEnabledFlag": [
+        {
+            "platforms": [
+                "android",
+                "ios",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "UseCECFlagInPolicyData"
+                    ]
+                }
+            ]
+        }
+    ],
     "UseCompositorClockVSyncInterval": [
         {
             "platforms": [
@@ -27768,22 +27834,15 @@
             ]
         }
     ],
-    "WebUITabStrip": [
+    "WebUITabStripDeprecation": [
         {
             "platforms": [
-                "linux",
-                "windows"
+                "chromeos"
             ],
             "experiments": [
                 {
-                    "name": "Enabled",
-                    "params": {
-                        "availability": "any",
-                        "event_trigger": "name:webui_tab_strip_iph_triggered;comparator:<3;window:1;storage:365",
-                        "event_used": "name:webui_tab_strip_opened;comparator:==0;window:90;storage:365",
-                        "session_rate": "any"
-                    },
-                    "enable_features": [
+                    "name": "Treatment",
+                    "disable_features": [
                         "WebUITabStrip"
                     ]
                 }
diff --git a/third_party/android_deps/autorolled/build.gradle.template b/third_party/android_deps/autorolled/build.gradle.template
index 1daab1c9..bab639b 100644
--- a/third_party/android_deps/autorolled/build.gradle.template
+++ b/third_party/android_deps/autorolled/build.gradle.template
@@ -62,6 +62,7 @@
     compileLatest 'com.google.android.gms:play-services-instantapps:+'
     compileLatest 'com.google.android.gms:play-services-location:+'
     compileLatest 'com.google.android.gms:play-services-tasks:+'
+    compileLatest 'com.google.android.gms:play-services-time:+'
     compileLatest 'com.google.android.gms:play-services-vision-common:+'
     compileLatest 'com.google.android.gms:play-services-vision:+'
     compileLatest 'com.google.firebase:firebase-messaging:+'
diff --git a/third_party/angle b/third_party/angle
index 306e58a9..173b937 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 306e58a9fb0b1d8500165d6bde1796061ab97ba6
+Subproject commit 173b937deb470f2972e1af58206eae43813412e4
diff --git a/third_party/blink/public/blink_resources.grd b/third_party/blink/public/blink_resources.grd
index 9ee612ee..8812516 100644
--- a/third_party/blink/public/blink_resources.grd
+++ b/third_party/blink/public/blink_resources.grd
@@ -31,6 +31,7 @@
       <include name="IDR_UASTYLE_TRANSITION_SCOPED_CSS" file="../renderer/core/css/transition_scoped.css" type="BINDATA" compress="brotli"/>
       <include name="IDR_UASTYLE_TRANSITION_ANIMATIONS_CSS" file="../renderer/core/css/transition_animations.css" type="BINDATA" compress="brotli"/>
       <include name="IDR_UASTYLE_TRANSITION_ANIMATIONS_SCOPED_CSS" file="../renderer/core/css/transition_animations_scoped.css" type="BINDATA" compress="brotli"/>
+      <include name="IDR_UASTYLE_OVERSCROLL_CSS" file="../renderer/core/css/overscroll.css" type="BINDATA" compress="brotli"/>
       <include name="IDR_DOCUMENTXMLTREEVIEWER_CSS" file="../renderer/core/xml/DocumentXMLTreeViewer.css" type="BINDATA" compress="brotli"/>
       <include name="IDR_DOCUMENTXMLTREEVIEWER_JS" file="../renderer/core/xml/DocumentXMLTreeViewer.js" type="BINDATA" compress="brotli"/>
       <include name="IDR_VALIDATION_BUBBLE_ICON" file="../renderer/core/html/forms/resources/input_alert.svg" type="BINDATA" compress="brotli"/>
diff --git a/third_party/blink/public/devtools_protocol/domains/Page.pdl b/third_party/blink/public/devtools_protocol/domains/Page.pdl
index c88a780..bd96c7c 100644
--- a/third_party/blink/public/devtools_protocol/domains/Page.pdl
+++ b/third_party/blink/public/devtools_protocol/domains/Page.pdl
@@ -166,6 +166,7 @@
       local-fonts
       local-network-access
       magnetometer
+      manual-text
       media-playback-while-not-visible
       microphone
       midi
diff --git a/third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom b/third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom
index 45123a19..b2836f1 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom
@@ -871,7 +871,7 @@
     // kViewTransitionCaptureMode = 810,
     kInteractivity = 811,
     kGridLanesFill = 812,
-    kMasonryDirection = 813,
+    kGridLanesDirection = 813,
     kGridLanesFlow = 814,
     // kMasonryAutoTracks = 815,
     kResult = 816,
diff --git a/third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom
index b2d3579..0620e20 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom
@@ -367,7 +367,7 @@
   kAppShortcuts = 305,
   kFetchlater = 306,
   kDRAFT_WasmBranchHinting = 307,
-  kDRAFT_ExplicitResourceManagement = 308,
+  kExplicitResourceManagement = 308,
   kAppLaunchHandler = 309,
   kFunction = 310,
   kIf = 311,
diff --git a/third_party/blink/public/web/web_autofill_client.h b/third_party/blink/public/web/web_autofill_client.h
index 24e1146..c4baa63 100644
--- a/third_party/blink/public/web/web_autofill_client.h
+++ b/third_party/blink/public/web/web_autofill_client.h
@@ -98,6 +98,10 @@
   // Called when the given form element is reset.
   virtual void FormElementReset(const WebFormElement&) {}
 
+  // Called when DevTools is connected or disconnect to the frame.
+  // The document is not fired again when the document changes.
+  virtual void OnDevToolsSessionConnectionChanged(bool attached) {}
+
   // Determines the form-related issues in the WebAutofillClient's document and
   // adds them to the associated frame's DevTools issues.
   virtual void EmitFormIssuesToDevtools() {}
diff --git a/third_party/blink/renderer/bindings/core/v8/use_counter_callback.cc b/third_party/blink/renderer/bindings/core/v8/use_counter_callback.cc
index f30976b..81d6796ab 100644
--- a/third_party/blink/renderer/bindings/core/v8/use_counter_callback.cc
+++ b/third_party/blink/renderer/bindings/core/v8/use_counter_callback.cc
@@ -467,7 +467,7 @@
       webdx_feature = WebDXFeature::kDRAFT_WasmBranchHinting;
       break;
     case v8::Isolate::kExplicitResourceManagement:
-      webdx_feature = WebDXFeature::kDRAFT_ExplicitResourceManagement;
+      webdx_feature = WebDXFeature::kExplicitResourceManagement;
       break;
     case v8::Isolate::kUint8ArrayToFromBase64AndHex:
       webdx_feature = WebDXFeature::kUint8ArrayBase64Hex;
diff --git a/third_party/blink/renderer/core/css/css_default_style_sheets.cc b/third_party/blink/renderer/core/css/css_default_style_sheets.cc
index ec9ee022..de073eef 100644
--- a/third_party/blink/renderer/core/css/css_default_style_sheets.cc
+++ b/third_party/blink/renderer/core/css/css_default_style_sheets.cc
@@ -49,6 +49,7 @@
 #include "third_party/blink/renderer/core/html/media/html_video_element.h"
 #include "third_party/blink/renderer/core/layout/layout_theme.h"
 #include "third_party/blink/renderer/core/mathml_names.h"
+#include "third_party/blink/renderer/core/style/computed_style_constants.h"
 #include "third_party/blink/renderer/platform/data_resource_helper.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
@@ -144,6 +145,7 @@
   marker_style_sheet_.Clear();
   scroll_button_style_sheet_.Clear();
   scroll_marker_style_sheet_.Clear();
+  overscroll_style_sheet_.Clear();
   permission_element_style_sheet_.Clear();
   view_source_style_sheet_.Clear();
   json_style_sheet_.Clear();
@@ -466,6 +468,21 @@
       rule_set_group_cache_.clear();
       return true;
     }
+    case kPseudoIdOverscrollAreaParent:
+    case kPseudoIdOverscrollClientArea: {
+      if (overscroll_style_sheet_) {
+        return false;
+      }
+      overscroll_style_sheet_ = ParseUASheet(
+          UncompressResourceAsASCIIString(IDR_UASTYLE_OVERSCROLL_CSS));
+      if (!default_pseudo_element_style_) {
+        default_pseudo_element_style_ = MakeGarbageCollected<RuleSet>();
+      }
+      default_pseudo_element_style_->AddRulesFromSheet(
+          OverscrollStyleSheet(), ScreenEval(), /*mixins=*/{});
+      default_pseudo_element_style_->CompactRulesIfNeeded();
+      return true;
+    }
     case kPseudoIdMarker: {
       if (marker_style_sheet_) {
         return false;
@@ -643,6 +660,7 @@
   visitor->Trace(marker_style_sheet_);
   visitor->Trace(scroll_button_style_sheet_);
   visitor->Trace(scroll_marker_style_sheet_);
+  visitor->Trace(overscroll_style_sheet_);
   visitor->Trace(view_source_style_sheet_);
   visitor->Trace(json_style_sheet_);
 
diff --git a/third_party/blink/renderer/core/css/css_default_style_sheets.h b/third_party/blink/renderer/core/css/css_default_style_sheets.h
index 250591cc..17ab296 100644
--- a/third_party/blink/renderer/core/css/css_default_style_sheets.h
+++ b/third_party/blink/renderer/core/css/css_default_style_sheets.h
@@ -100,6 +100,9 @@
   StyleSheetContents* ScrollMarkerStyleSheet() {
     return scroll_marker_style_sheet_.Get();
   }
+  StyleSheetContents* OverscrollStyleSheet() {
+    return overscroll_style_sheet_.Get();
+  }
   StyleSheetContents* ForcedColorsStyleSheet() {
     return forced_colors_style_sheet_.Get();
   }
@@ -189,6 +192,7 @@
   Member<StyleSheetContents> marker_style_sheet_;
   Member<StyleSheetContents> scroll_button_style_sheet_;
   Member<StyleSheetContents> scroll_marker_style_sheet_;
+  Member<StyleSheetContents> overscroll_style_sheet_;
   Member<StyleSheetContents> forced_colors_style_sheet_;
   Member<StyleSheetContents> view_source_style_sheet_;
   Member<StyleSheetContents> json_style_sheet_;
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 8f0fa9b..fc538d5 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -3493,6 +3493,17 @@
       invalidate: ["layout", "paint"],
     },
     {
+      name: "grid-lanes-direction",
+      property_methods: ["CSSValueFromComputedStyleInternal"],
+      field_group: "*",
+      field_template: "keyword",
+      typedom_types: ["Keyword"],
+      keywords: ["row", "row-reverse", "column", "column-reverse"],
+      default_value: "column",
+      invalidate: ["layout", "paint"],
+      runtime_flag: "CSSMasonryLayout",
+    },
+    {
       name: "grid-lanes-fill",
       property_methods: ["CSSValueFromComputedStyleInternal"],
       field_group: "*",
@@ -4081,17 +4092,6 @@
       invalidate: ["paint"],
     },
     {
-      name: "masonry-direction",
-      property_methods: ["CSSValueFromComputedStyleInternal"],
-      field_group: "*",
-      field_template: "keyword",
-      typedom_types: ["Keyword"],
-      keywords: ["row", "row-reverse", "column", "column-reverse"],
-      default_value: "column",
-      invalidate: ["layout", "paint"],
-      runtime_flag: "CSSMasonryLayout",
-    },
-    {
       name: "item-tolerance",
       include_paths: ["third_party/blink/renderer/core/style/item_tolerance.h"],
       property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal"],
@@ -8731,14 +8731,14 @@
     },
     {
       name: "grid-lanes",
-      longhands: [ "grid-template-areas", "grid-template-columns", "masonry-direction", "grid-lanes-fill"],
+      longhands: [ "grid-template-areas", "grid-template-columns", "grid-lanes-direction", "grid-lanes-fill"],
       property_methods: ["ParseShorthand", "CSSValueFromComputedStyleInternal"],
       layout_dependent: true,
       runtime_flag: "CSSMasonryLayout",
     },
     {
       name: "grid-lanes-flow",
-      longhands: ["masonry-direction", "grid-lanes-fill"],
+      longhands: ["grid-lanes-direction", "grid-lanes-fill"],
       property_methods: ["ParseShorthand", "CSSValueFromComputedStyleInternal"],
       runtime_flag: "CSSMasonryLayout",
     },
diff --git a/third_party/blink/renderer/core/css/css_property_equality.cc b/third_party/blink/renderer/core/css/css_property_equality.cc
index 022b572..d73a877 100644
--- a/third_party/blink/renderer/core/css/css_property_equality.cc
+++ b/third_party/blink/renderer/core/css/css_property_equality.cc
@@ -459,6 +459,8 @@
       return a.GridColumnEnd() == b.GridColumnEnd();
     case CSSPropertyID::kGridColumnStart:
       return a.GridColumnStart() == b.GridColumnStart();
+    case CSSPropertyID::kGridLanesDirection:
+      return a.GridLanesDirection() == b.GridLanesDirection();
     case CSSPropertyID::kGridLanesFill:
       return a.GridLanesFill() == b.GridLanesFill();
     case CSSPropertyID::kGridRowEnd:
@@ -538,8 +540,6 @@
       return a.MarkerStartResource() == b.MarkerStartResource();
     case CSSPropertyID::kMaskType:
       return a.MaskType() == b.MaskType();
-    case CSSPropertyID::kMasonryDirection:
-      return a.MasonryDirection() == b.MasonryDirection();
     case CSSPropertyID::kMaxLines:
       return a.MaxLines() == b.MaxLines();
     case CSSPropertyID::kItemTolerance:
diff --git a/third_party/blink/renderer/core/css/css_selector.cc b/third_party/blink/renderer/core/css/css_selector.cc
index 385116e..176890e 100644
--- a/third_party/blink/renderer/core/css/css_selector.cc
+++ b/third_party/blink/renderer/core/css/css_selector.cc
@@ -577,6 +577,8 @@
     {"-internal-menulist-popover-with-menulist-anchor",
      CSSSelector::kPseudoMenulistPopoverWithMenulistAnchor},
     {"-internal-multi-select-focus", CSSSelector::kPseudoMultiSelectFocus},
+    {"-internal-overscroll-client-area",
+     CSSSelector::kPseudoOverscrollClientArea},
     {"-internal-popover-in-top-layer", CSSSelector::kPseudoPopoverInTopLayer},
     {"-internal-relative-anchor", CSSSelector::kPseudoRelativeAnchor},
     {"-internal-selector-fragment-anchor",
@@ -698,6 +700,8 @@
 };
 
 constexpr static NameToPseudoStruct kPseudoTypeWithArgumentsMap[] = {
+    {"-internal-overscroll-area-parent",
+     CSSSelector::kPseudoOverscrollAreaParent},
     {"-webkit-any", CSSSelector::kPseudoAny},
     {"active-view-transition-type",
      CSSSelector::kPseudoActiveViewTransitionType},
@@ -839,6 +843,12 @@
     return CSSSelector::kPseudoUnknown;
   }
 
+  if ((match->type == CSSSelector::kPseudoOverscrollAreaParent ||
+       match->type == CSSSelector::kPseudoOverscrollClientArea) &&
+      !RuntimeEnabledFeatures::CSSOverscrollGesturesEnabled()) {
+    return CSSSelector::kPseudoUnknown;
+  }
+
   return static_cast<CSSSelector::PseudoType>(match->type);
 }
 
diff --git a/third_party/blink/renderer/core/css/css_value_keywords.json5 b/third_party/blink/renderer/core/css/css_value_keywords.json5
index 49778ad..a9608fd2 100644
--- a/third_party/blink/renderer/core/css/css_value_keywords.json5
+++ b/third_party/blink/renderer/core/css/css_value_keywords.json5
@@ -1228,6 +1228,12 @@
     // grid-{column|row}-{start|end}
     "span",
 
+    // grid-lanes-direction
+    // row
+    // row-reverse
+    // column
+    // column-reverse
+
     // grid-lanes-fill
     // normal
     // reverse
@@ -1257,12 +1263,6 @@
     // luminance
     "match-source",
 
-    // masonry-direction
-    // row
-    // row-reverse
-    // column
-    // column-reverse
-
     // color-interpolation / color-interpolation-filters
     // auto
     "srgb",
diff --git a/third_party/blink/renderer/core/css/cssom_utils.cc b/third_party/blink/renderer/core/css/cssom_utils.cc
index 73bc256..d56938f 100644
--- a/third_party/blink/renderer/core/css/cssom_utils.cc
+++ b/third_party/blink/renderer/core/css/cssom_utils.cc
@@ -71,13 +71,14 @@
 }
 
 // static
-bool CSSOMUtils::IsMasonryColumnDirectionValue(
-    const CSSValue* masonry_direction_values) {
-  const auto* masonry_direction_value =
-      DynamicTo<CSSIdentifierValue>(masonry_direction_values);
-  return masonry_direction_value &&
-         (masonry_direction_value->GetValueID() == CSSValueID::kColumn ||
-          masonry_direction_value->GetValueID() == CSSValueID::kColumnReverse);
+bool CSSOMUtils::IsGridLanesColumnDirectionValue(
+    const CSSValue* grid_lanes_direction_values) {
+  const auto* grid_lanes_direction_value =
+      DynamicTo<CSSIdentifierValue>(grid_lanes_direction_values);
+  return grid_lanes_direction_value &&
+         (grid_lanes_direction_value->GetValueID() == CSSValueID::kColumn ||
+          grid_lanes_direction_value->GetValueID() ==
+              CSSValueID::kColumnReverse);
 }
 
 // static
@@ -213,15 +214,15 @@
 
 // static
 CSSValueList* CSSOMUtils::ComputedValueForGridLanesShorthand(
-    const CSSValue* masonry_template_tracks_values,
+    const CSSValue* grid_template_tracks_values,
     const CSSValue* template_area_values,
-    const CSSValue* masonry_direction_values,
+    const CSSValue* grid_lanes_direction_values,
     const CSSValue* grid_lanes_fill_values) {
-  const bool has_initial_masonry_template_tracks =
-      IsNoneValue(masonry_template_tracks_values);
+  const bool has_initial_grid_template_tracks =
+      IsNoneValue(grid_template_tracks_values);
   const bool has_initial_template_areas = IsNoneValue(template_area_values);
   CSSValueList* list = CSSValueList::CreateSpaceSeparated();
-  if (has_initial_template_areas && has_initial_masonry_template_tracks) {
+  if (has_initial_template_areas && has_initial_grid_template_tracks) {
     list->Append(*template_area_values);
   }
 
@@ -229,7 +230,7 @@
     // If we have template columns, we can serialize the template areas as is.
     // Otherwise, for template rows, we need to serialize multiple string tokens
     // into a single space-separated string.
-    if (IsMasonryColumnDirectionValue(masonry_direction_values)) {
+    if (IsGridLanesColumnDirectionValue(grid_lanes_direction_values)) {
       list->Append(*template_area_values);
     } else {
       const cssvalue::CSSGridTemplateAreasValue* template_areas =
@@ -241,11 +242,11 @@
     }
   }
 
-  if (!has_initial_masonry_template_tracks) {
-    list->Append(*masonry_template_tracks_values);
+  if (!has_initial_grid_template_tracks) {
+    list->Append(*grid_template_tracks_values);
   }
 
-  list->Append(*masonry_direction_values);
+  list->Append(*grid_lanes_direction_values);
   list->Append(*grid_lanes_fill_values);
 
   return list;
diff --git a/third_party/blink/renderer/core/css/cssom_utils.h b/third_party/blink/renderer/core/css/cssom_utils.h
index 15d938c..5a0b0591 100644
--- a/third_party/blink/renderer/core/css/cssom_utils.h
+++ b/third_party/blink/renderer/core/css/cssom_utils.h
@@ -33,8 +33,8 @@
 
   static bool HasGridRepeatValue(const CSSValueList* value_list);
 
-  static bool IsMasonryColumnDirectionValue(
-      const CSSValue* masonry_direction_values);
+  static bool IsGridLanesColumnDirectionValue(
+      const CSSValue* grid_lanes_direction_values);
 
   // Returns the name of a grid area based on the position (`row`, `column`).
   // e.g. with the following grid definition:
@@ -68,12 +68,12 @@
       const CSSValue* template_column_values,
       const CSSValue* template_area_values);
   // Returns a `CSSValueList` containing the computed value for
-  // the `grid-lanes` shorthand, based on provided `masonry-template-tracks`,
-  // `grid-template-areas`, `masonry-direction`, and `grid-lanes-fill`.
+  // the `grid-lanes` shorthand, based on provided `grid-template-tracks`,
+  // `grid-template-areas`, `grid-lanes-direction`, and `grid-lanes-fill`.
   static CSSValueList* ComputedValueForGridLanesShorthand(
-      const CSSValue* masonry_template_tracks_values,
+      const CSSValue* grid_template_tracks_values,
       const CSSValue* template_area_values,
-      const CSSValue* masonry_direction_values,
+      const CSSValue* grid_lanes_direction_values,
       const CSSValue* grid_lanes_fill_values);
 };
 
diff --git a/third_party/blink/renderer/core/css/invalidation/rule_invalidation_data_visitor.cc b/third_party/blink/renderer/core/css/invalidation/rule_invalidation_data_visitor.cc
index e08c198..7aaae5642 100644
--- a/third_party/blink/renderer/core/css/invalidation/rule_invalidation_data_visitor.cc
+++ b/third_party/blink/renderer/core/css/invalidation/rule_invalidation_data_visitor.cc
@@ -101,6 +101,8 @@
     case CSSSelector::kPseudoLang:
     case CSSSelector::kPseudoDir:
     case CSSSelector::kPseudoNot:
+    case CSSSelector::kPseudoOverscrollAreaParent:
+    case CSSSelector::kPseudoOverscrollClientArea:
     case CSSSelector::kPseudoPlaceholder:
     case CSSSelector::kPseudoDetailsContent:
     case CSSSelector::kPseudoPermissionIcon:
diff --git a/third_party/blink/renderer/core/css/overscroll.css b/third_party/blink/renderer/core/css/overscroll.css
new file mode 100644
index 0000000..16b7223a
--- /dev/null
+++ b/third_party/blink/renderer/core/css/overscroll.css
@@ -0,0 +1,10 @@
+::-internal-overscroll-area-parent(*) {
+  display: block;
+  overflow: auto;
+  scrollbar-width: none;
+}
+
+::-internal-overscroll-client-area {
+  display: block;
+  overflow: inherit;
+}
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.cc b/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.cc
index aa9654a..002fa63 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.cc
@@ -1261,7 +1261,7 @@
     case CSSPropertyID::kMaskType:
       return value_id == CSSValueID::kLuminance ||
              value_id == CSSValueID::kAlpha;
-    case CSSPropertyID::kMasonryDirection:
+    case CSSPropertyID::kGridLanesDirection:
       return value_id == CSSValueID::kRow ||
              value_id == CSSValueID::kRowReverse ||
              value_id == CSSValueID::kColumn ||
@@ -1758,7 +1758,7 @@
     CSSPropertyID::kInterpolateSize,
     CSSPropertyID::kListStylePosition,
     CSSPropertyID::kMaskType,
-    CSSPropertyID::kMasonryDirection,
+    CSSPropertyID::kGridLanesDirection,
     CSSPropertyID::kMathShift,
     CSSPropertyID::kMathStyle,
     CSSPropertyID::kObjectFit,
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
index b5f4363..be8aefd 100644
--- a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
+++ b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
@@ -1866,6 +1866,16 @@
       output_.push_back(std::move(selector));
       return true;
     }
+    case CSSSelector::kPseudoOverscrollAreaParent: {
+      const CSSParserToken& ident = stream.Peek();
+      if (ident.GetType() == kDelimiterToken && ident.Delimiter() == '*') {
+        selector.SetArgument(AtomicString("*"));
+      } else {
+        return false;
+      }
+      output_.push_back(std::move(selector));
+      return true;
+    }
     case CSSSelector::kPseudoScrollButton: {
       const CSSParserToken& ident = stream.Peek();
       if (ident.GetType() == kIdentToken) {
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
index b624320..80addeb5 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
@@ -61,7 +61,7 @@
 #include "third_party/blink/renderer/core/layout/grid/layout_grid.h"
 #include "third_party/blink/renderer/core/layout/layout_block.h"
 #include "third_party/blink/renderer/core/layout/layout_box.h"
-#include "third_party/blink/renderer/core/layout/masonry/layout_masonry.h"
+#include "third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.h"
 #include "third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.h"
 #include "third_party/blink/renderer/core/layout/svg/transform_helper.h"
 #include "third_party/blink/renderer/core/style/computed_style_constants.h"
@@ -2072,7 +2072,7 @@
 
 template <typename T>
 typename std::enable_if<std::is_same<T, LayoutGrid>::value ||
-                            std::is_same<T, LayoutMasonry>::value,
+                            std::is_same<T, LayoutGridLanes>::value,
                         CSSValue*>::type
 ValueForGridTrackList(GridTrackSizingDirection direction,
                       const LayoutObject* layout_object,
@@ -2200,7 +2200,7 @@
     const ComputedStyle& style,
     bool force_computed_value) {
   if (style.IsDisplayGridLanesBox()) {
-    return blink::ValueForGridTrackList<LayoutMasonry>(
+    return blink::ValueForGridTrackList<LayoutGridLanes>(
         direction, layout_object, style, force_computed_value);
   }
   return blink::ValueForGridTrackList<LayoutGrid>(direction, layout_object,
@@ -5140,28 +5140,28 @@
   // Note: `shorthand.properties()[1]` is intentionally not used here because it
   // always refers to `grid-template-columns`.
   // Instead, we use `GetCSSPropertyGridTemplateColumns()` or
-  // `GetCSSPropertyGridTemplateRows()` depending on the `masonry-direction`,
+  // `GetCSSPropertyGridTemplateRows()` depending on the `grid-lanes-direction`,
   // since `grid-template-rows` is not listed in the `grid-lanes` shorthand
   // property.
-  const CSSValue* masonry_direction_values =
+  const CSSValue* grid_lanes_direction_values =
       shorthand.properties()[2]->CSSValueFromComputedStyle(
           style, layout_object, allow_visited_style, value_phase);
-  DCHECK(masonry_direction_values);
-  const CSSValue* masonry_template_tracks_values =
-      CSSOMUtils::IsMasonryColumnDirectionValue(masonry_direction_values)
+  DCHECK(grid_lanes_direction_values);
+  const CSSValue* grid_lanes_template_tracks_values =
+      CSSOMUtils::IsGridLanesColumnDirectionValue(grid_lanes_direction_values)
           ? GetCSSPropertyGridTemplateColumns().CSSValueFromComputedStyle(
                 style, layout_object, allow_visited_style, value_phase)
           : GetCSSPropertyGridTemplateRows().CSSValueFromComputedStyle(
                 style, layout_object, allow_visited_style, value_phase);
-  DCHECK(masonry_template_tracks_values);
+  DCHECK(grid_lanes_template_tracks_values);
   const CSSValue* grid_lanes_fill_values =
       shorthand.properties()[3]->CSSValueFromComputedStyle(
           style, layout_object, allow_visited_style, value_phase);
   DCHECK(grid_lanes_fill_values);
 
   return CSSOMUtils::ComputedValueForGridLanesShorthand(
-      masonry_template_tracks_values, template_area_values,
-      masonry_direction_values, grid_lanes_fill_values);
+      grid_lanes_template_tracks_values, template_area_values,
+      grid_lanes_direction_values, grid_lanes_fill_values);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
index 7deee045..688de44 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
@@ -142,7 +142,7 @@
   return IdentMatches<CSSValueID::kUnsafe, CSSValueID::kSafe>(id);
 }
 
-bool IsMasonryDirectionOrFillKeyword(CSSValueID id) {
+bool IsGridLanesDirectionOrFillKeyword(CSSValueID id) {
   return IdentMatches<CSSValueID::kRow, CSSValueID::kRowReverse,
                       CSSValueID::kColumn, CSSValueID::kColumnReverse,
                       CSSValueID::kNormal, CSSValueID::kReverse>(id);
@@ -6907,7 +6907,7 @@
   auto HasMoreGridLanesValues = [](CSSParserTokenStream& stream,
                                    bool is_grid_lanes_shorthand) -> bool {
     return (is_grid_lanes_shorthand &&
-            IsMasonryDirectionOrFillKeyword(stream.Peek().Id()));
+            IsGridLanesDirectionOrFillKeyword(stream.Peek().Id()));
   };
 
   do {
@@ -7144,27 +7144,29 @@
   return false;
 }
 
-CSSValue* ParseMasonryTemplateAreasValue(const String& masonry_template_areas,
-                                         bool is_template_columns) {
+CSSValue* ParseGridLanesTemplateAreasValue(
+    const String& grid_lanes_template_areas,
+    bool is_template_columns) {
   NamedGridAreaMap grid_area_map;
   wtf_size_t row_count = 0;
   wtf_size_t column_count = 0;
 
   if (is_template_columns) {
-    // For template-columns, we treat the `masonry_template_areas` string
+    // For template-columns, we treat the `grid_lanes_template_areas` string
     // as a single row of grid areas and use the function below to construct the
     // `grid_area_map`.
-    if (!css_parsing_utils::ParseGridTemplateAreasRow(
-            masonry_template_areas, grid_area_map, row_count, column_count)) {
+    if (!css_parsing_utils::ParseGridTemplateAreasRow(grid_lanes_template_areas,
+                                                      grid_area_map, row_count,
+                                                      column_count)) {
       return nullptr;
     }
     ++row_count;
   } else {
-    // For template-rows, we need to convert the `masonry_template_areas` string
-    // into appropriate row values. For example, we want to transform "a b c"
-    // into separate row strings: "a", "b", "c".
+    // For template-rows, we need to convert the `grid_lanes_template_areas`
+    // string into appropriate row values. For example, we want to transform "a
+    // b c" into separate row strings: "a", "b", "c".
     Vector<String> rows =
-        ParseGridTemplateAreasColumnNames(masonry_template_areas);
+        ParseGridTemplateAreasColumnNames(grid_lanes_template_areas);
     for (const String& row : rows) {
       if (!css_parsing_utils::ParseGridTemplateAreasRow(
               row, grid_area_map, row_count, column_count)) {
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
index 6a47cee..c4a2217 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
@@ -578,8 +578,9 @@
                                   const CSSValue*& template_columns,
                                   const CSSValue*& template_areas);
 
-CSSValue* ParseMasonryTemplateAreasValue(const String& masonry_template_areas,
-                                         bool is_template_columns);
+CSSValue* ParseGridLanesTemplateAreasValue(
+    const String& grid_lanes_template_areas,
+    bool is_template_columns);
 
 CSSValue* ConsumeItemTolerance(CSSParserTokenStream&, const CSSParserContext&);
 
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index a6c64960..d2c965c 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -5324,6 +5324,14 @@
   return ComputedStyleUtils::ValueForGridPosition(style.GridColumnStart());
 }
 
+const CSSValue* GridLanesDirection::CSSValueFromComputedStyleInternal(
+    const ComputedStyle& style,
+    const LayoutObject*,
+    bool allow_visited_style,
+    CSSValuePhase value_phase) const {
+  return CSSIdentifierValue::Create(style.GridLanesDirection());
+}
+
 const CSSValue* GridLanesFill::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
     const LayoutObject*,
@@ -7140,14 +7148,6 @@
   return CSSIdentifierValue::Create(style.MaskType());
 }
 
-const CSSValue* MasonryDirection::CSSValueFromComputedStyleInternal(
-    const ComputedStyle& style,
-    const LayoutObject*,
-    bool allow_visited_style,
-    CSSValuePhase value_phase) const {
-  return CSSIdentifierValue::Create(style.MasonryDirection());
-}
-
 const CSSValue* ItemTolerance::ParseSingleValue(
     CSSParserTokenStream& stream,
     const CSSParserContext& context,
diff --git a/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc b/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
index 9b1afd9..a44d57c 100644
--- a/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
@@ -2752,29 +2752,29 @@
     const CSSParserContext& context,
     const CSSParserLocalContext&,
     HeapVector<CSSPropertyValue, 64>& properties) const {
-  String masonry_template_areas;
+  String grid_lanes_template_areas;
   bool is_template_columns = true;
   const CSSValue* template_areas =
       GetCSSPropertyGridTemplateAreas().InitialValue();
-  const CSSValue* masonry_direction =
+  const CSSValue* grid_lanes_direction =
       CSSIdentifierValue::Create(CSSValueID::kColumn);
   const CSSValue* grid_lanes_fill =
       CSSIdentifierValue::Create(CSSValueID::kNormal);
 
-  // Retrieve the string of `masonry_template_areas`. We'll parse it into
-  // appropriate `grid-template-areas` based on the `masonry-direction`.
+  // Retrieve the string of `grid_lanes_template_areas`. We'll parse it into
+  // appropriate `grid-template-areas` based on the `grid-lanes-direction`.
   if (stream.Peek().GetType() == kStringToken) {
-    masonry_template_areas =
+    grid_lanes_template_areas =
         stream.ConsumeIncludingWhitespace().Value().ToString();
   }
 
-  // Retrieve the `masonry_template_tracks`, which can be either
+  // Retrieve the `grid_lanes_template_tracks`, which can be either
   // `grid-template-columns` or `grid-template-rows`
-  const CSSValue* masonry_template_tracks =
+  const CSSValue* grid_lanes_template_tracks =
       css_parsing_utils::ConsumeGridTemplatesRowsOrColumns(
           stream, context,
           /*is_grid_lanes_shorthand=*/true);
-  if (!masonry_template_tracks) {
+  if (!grid_lanes_template_tracks) {
     return false;
   }
   stream.ConsumeWhitespace();
@@ -2788,7 +2788,7 @@
             stream.Peek().Id())) {
       is_template_columns = false;
     }
-    masonry_direction = css_parsing_utils::ConsumeIdent(stream);
+    grid_lanes_direction = css_parsing_utils::ConsumeIdent(stream);
   }
 
   if (css_parsing_utils::IdentMatches<CSSValueID::kNormal,
@@ -2804,18 +2804,18 @@
     return false;
   }
 
-  // Parse `masonry_template_areas` into the appropriate `grid-template-areas`
-  // value.
-  // - `masonry_template_areas` is a single space-separated string.
-  // - If `masonry-direction` is column, use the string as a single row (e.g.,
-  // "a b c d" -> "a b c d").
-  // - If `masonry-direction` is row, split the string into multiple rows, one
-  // per area name (e.g., "a b c d" -> "a" "b" "c" "d"). This ensures the
+  // Parse `grid_lanes_template_areas` into the appropriate
+  // `grid-template-areas` value.
+  // - `grid_lanes_template_areas` is a single space-separated string.
+  // - If `grid-lanes-direction` is column, use the string as a single row
+  // (e.g., "a b c d" -> "a b c d").
+  // - If `grid-lanes-direction` is row, split the string into multiple rows,
+  // one per area name (e.g., "a b c d" -> "a" "b" "c" "d"). This ensures the
   // correct mapping to the CSS `grid-template-areas` syntax based on the
-  // `masonry-direction`.
-  if (!masonry_template_areas.ContainsOnlyWhitespaceOrEmpty()) {
-    template_areas = css_parsing_utils::ParseMasonryTemplateAreasValue(
-        masonry_template_areas, is_template_columns);
+  // `grid-lanes-direction`.
+  if (!grid_lanes_template_areas.ContainsOnlyWhitespaceOrEmpty()) {
+    template_areas = css_parsing_utils::ParseGridLanesTemplateAreasValue(
+        grid_lanes_template_areas, is_template_columns);
     if (!template_areas) {
       return false;
     }
@@ -2827,7 +2827,7 @@
   if (is_template_columns) {
     css_parsing_utils::AddProperty(
         CSSPropertyID::kGridTemplateColumns, CSSPropertyID::kGridLanes,
-        *masonry_template_tracks, important,
+        *grid_lanes_template_tracks, important,
         css_parsing_utils::IsImplicitProperty::kNotImplicit, properties);
   } else {
     // For `grid_template_rows`, since it is not included in the grid-lanes
@@ -2835,11 +2835,11 @@
     // using the AddProperty helper.
     properties.push_back(
         CSSPropertyValue(CSSPropertyName(CSSPropertyID::kGridTemplateRows),
-                         *masonry_template_tracks, important));
+                         *grid_lanes_template_tracks, important));
   }
   css_parsing_utils::AddProperty(
-      CSSPropertyID::kMasonryDirection, CSSPropertyID::kGridLanes,
-      *masonry_direction, important,
+      CSSPropertyID::kGridLanesDirection, CSSPropertyID::kGridLanes,
+      *grid_lanes_direction, important,
       css_parsing_utils::IsImplicitProperty::kNotImplicit, properties);
   css_parsing_utils::AddProperty(
       CSSPropertyID::kGridLanesFill, CSSPropertyID::kGridLanes,
@@ -2874,16 +2874,16 @@
       gridLanesFlowShorthand().properties();
   DCHECK_EQ(longhands.size(), 2u);
 
-  if (longhands[0]->PropertyID() != CSSPropertyID::kMasonryDirection ||
+  if (longhands[0]->PropertyID() != CSSPropertyID::kGridLanesDirection ||
       longhands[1]->PropertyID() != CSSPropertyID::kGridLanesFill) {
     return false;
   }
 
-  const CSSValue* masonry_direction = css_parsing_utils::ParseLonghand(
+  const CSSValue* grid_lanes_direction = css_parsing_utils::ParseLonghand(
       longhands[0]->PropertyID(), gridLanesFlowShorthand().id(), context,
       stream);
 
-  if (!masonry_direction) {
+  if (!grid_lanes_direction) {
     return false;
   }
 
@@ -2896,7 +2896,7 @@
   }
 
   AddProperty(longhands[0]->PropertyID(), gridLanesFlowShorthand().id(),
-              *masonry_direction, important,
+              *grid_lanes_direction, important,
               css_parsing_utils::IsImplicitProperty::kNotImplicit, properties);
   AddProperty(longhands[1]->PropertyID(), gridLanesFlowShorthand().id(),
               *grid_lanes_fill, important,
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index 1645719..bd78b6e 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -155,6 +155,8 @@
     case kPseudoIdScrollButtonInlineEnd:
     case kPseudoIdScrollButtonBlockEnd:
     case kPseudoIdScrollMarker:
+    case kPseudoIdOverscrollAreaParent:
+    case kPseudoIdOverscrollClientArea:
       return true;
     default:
       return false;
diff --git a/third_party/blink/renderer/core/css/rule_set.cc b/third_party/blink/renderer/core/css/rule_set.cc
index 57178db..59f182a 100644
--- a/third_party/blink/renderer/core/css/rule_set.cc
+++ b/third_party/blink/renderer/core/css/rule_set.cc
@@ -279,6 +279,8 @@
     case CSSSelector::kPseudoViewTransitionNew:
     case CSSSelector::kPseudoViewTransitionOld:
     case CSSSelector::kPseudoScrollMarkerGroup:
+    case CSSSelector::kPseudoOverscrollAreaParent:
+    case CSSSelector::kPseudoOverscrollClientArea:
       return true;
     case CSSSelector::kPseudoCue:
     case CSSSelector::kPseudoFirstLine:
diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc
index ce8032b..8d9fe1f 100644
--- a/third_party/blink/renderer/core/css/selector_checker.cc
+++ b/third_party/blink/renderer/core/css/selector_checker.cc
@@ -3143,6 +3143,10 @@
       result.dynamic_pseudo = context.pseudo_id;
       return true;
     }
+    case CSSSelector::kPseudoOverscrollAreaParent:
+    case CSSSelector::kPseudoOverscrollClientArea: {
+      return element.GetPseudoIdForStyling() == pseudo_id;
+    }
     case CSSSelector::kPseudoScrollButton:
       return MatchScrollButton(element, context, result);
     case CSSSelector::kPseudoTargetText:
diff --git a/third_party/blink/renderer/core/css/style_property_serializer.cc b/third_party/blink/renderer/core/css/style_property_serializer.cc
index eab7a9d..38f54fc 100644
--- a/third_party/blink/renderer/core/css/style_property_serializer.cc
+++ b/third_party/blink/renderer/core/css/style_property_serializer.cc
@@ -2589,23 +2589,23 @@
   // `GetCSSPropertyGridTemplateRows()` depending on the `grid-lanes` direction,
   // since `grid-template-rows` is not listed in the `grid-lanes` shorthand
   // property.
-  const auto* masonry_direction_values =
+  const auto* grid_lanes_direction_values =
       property_set_.GetPropertyCSSValue(*shorthand.properties()[2]);
-  DCHECK(masonry_direction_values);
-  const auto* masonry_template_tracks_values =
-      CSSOMUtils::IsMasonryColumnDirectionValue(masonry_direction_values)
+  DCHECK(grid_lanes_direction_values);
+  const auto* grid_lanes_template_tracks_values =
+      CSSOMUtils::IsGridLanesColumnDirectionValue(grid_lanes_direction_values)
           ? property_set_.GetPropertyCSSValue(
                 GetCSSPropertyGridTemplateColumns())
           : property_set_.GetPropertyCSSValue(GetCSSPropertyGridTemplateRows());
-  DCHECK(masonry_template_tracks_values);
+  DCHECK(grid_lanes_template_tracks_values);
   const auto* grid_lanes_fill_values =
       property_set_.GetPropertyCSSValue(*shorthand.properties()[3]);
   DCHECK(grid_lanes_fill_values);
 
   const CSSValueList* grid_lanes_list =
       CSSOMUtils::ComputedValueForGridLanesShorthand(
-          masonry_template_tracks_values, template_area_values,
-          masonry_direction_values, grid_lanes_fill_values);
+          grid_lanes_template_tracks_values, template_area_values,
+          grid_lanes_direction_values, grid_lanes_fill_values);
   return grid_lanes_list->CssText();
 }
 
diff --git a/third_party/blink/renderer/core/dom/element_test.cc b/third_party/blink/renderer/core/dom/element_test.cc
index f18cd8c..7e17e2c 100644
--- a/third_party/blink/renderer/core/dom/element_test.cc
+++ b/third_party/blink/renderer/core/dom/element_test.cc
@@ -28,6 +28,7 @@
 #include "third_party/blink/renderer/core/html/html_plugin_element.h"
 #include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
+#include "third_party/blink/renderer/core/style/computed_style_base_constants.h"
 #include "third_party/blink/renderer/core/style/computed_style_constants.h"
 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -1472,7 +1473,7 @@
   EXPECT_EQ(nullptr, target_option->GetPseudoElement(kPseudoIdPickerIcon));
 }
 
-TEST_F(ElementTest, GenerateOverscrollPseudoElements) {
+TEST_F(ElementTest, OverscrollPseudoElementLayoutStructure) {
   ScopedCSSOverscrollGesturesForTest enabled(true);
   GetDocument().body()->SetInnerHTMLWithoutTrustedTypes(R"HTML(
     <style>
@@ -1511,15 +1512,9 @@
                                           AtomicString("--baz")));
 
   // Parentage of children and pseudos within content:
-  EXPECT_TRUE(scroller->GetPseudoElement(kPseudoIdBefore)
-                  ->GetLayoutObject()
-                  ->Parent()
-                  ->IsAnonymousBlockFlow());
-  EXPECT_EQ(scroller->GetPseudoElement(kPseudoIdBefore)
-                ->GetLayoutObject()
-                ->Parent()
-                ->Parent(),
-            overscroll_client_area->GetLayoutObject());
+  EXPECT_EQ(
+      scroller->GetPseudoElement(kPseudoIdBefore)->GetLayoutObject()->Parent(),
+      overscroll_client_area->GetLayoutObject());
   EXPECT_EQ(GetElementById("child")->GetLayoutObject()->PreviousSibling(),
             scroller->GetPseudoElement(kPseudoIdBefore)->GetLayoutObject());
 
@@ -1595,6 +1590,78 @@
             scroller->GetLayoutObject());
 }
 
+TEST_F(ElementTest, OverscrollPseudoElementStyles) {
+  ScopedCSSOverscrollGesturesForTest enabled(true);
+  GetDocument().body()->SetInnerHTMLWithoutTrustedTypes(R"HTML(
+    <style>
+      #scroller, #non-scroller {
+        overscroll-area: --foo;
+      }
+      #scroller {
+        overflow: auto;
+      }
+      /* Only UA stylesheets should be able to style these pseudo-elements.
+       * The following styles SHOULD NOT apply. */
+      #scroller::-internal-overscroll-area-parent(*),
+      #non-scroller::-internal-overscroll-area-parent(*) {
+        backface-visibility: hidden;
+      }
+      #scroller::-internal-overscroll-client-area,
+      #non-scroller::-internal-overscroll-client-area {
+        backface-visibility: hidden;
+      }
+    </style>
+    <div id="scroller"></div>
+    <div id="non-scroller"></div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+
+  Element* scroller = GetElementById("scroller");
+  Element* non_scroller = GetElementById("non-scroller");
+  PseudoElement* scroller_client_area =
+      scroller->GetPseudoElement(kPseudoIdOverscrollClientArea);
+  PseudoElement* non_scroller_client_area =
+      non_scroller->GetPseudoElement(kPseudoIdOverscrollClientArea);
+  PseudoElement* overscroll_parent_foo = scroller->GetPseudoElement(
+      kPseudoIdOverscrollAreaParent, AtomicString("--foo"));
+
+  ASSERT_TRUE(scroller_client_area);
+  ASSERT_TRUE(non_scroller_client_area);
+  ASSERT_TRUE(overscroll_parent_foo);
+
+  // Computed style of the overscroll area parent pseudo-elements
+  EXPECT_EQ(EOverflow::kAuto,
+            overscroll_parent_foo->GetComputedStyle()->OverflowX());
+  EXPECT_EQ(EOverflow::kAuto,
+            overscroll_parent_foo->GetComputedStyle()->OverflowY());
+  EXPECT_EQ(EScrollbarWidth::kNone,
+            overscroll_parent_foo->GetComputedStyle()->ScrollbarWidth());
+
+  // The scroller client area should be scrollable
+  EXPECT_EQ(EOverflow::kAuto,
+            scroller_client_area->GetComputedStyle()->OverflowY());
+
+  // Computed style of the overscroll area parent pseudo-elements
+  EXPECT_EQ(EOverflow::kAuto,
+            overscroll_parent_foo->GetComputedStyle()->OverflowX());
+  EXPECT_EQ(EOverflow::kAuto,
+            overscroll_parent_foo->GetComputedStyle()->OverflowY());
+  EXPECT_EQ(EScrollbarWidth::kNone,
+            overscroll_parent_foo->GetComputedStyle()->ScrollbarWidth());
+
+  // The non scroller client area is not scrollable
+  EXPECT_EQ(EOverflow::kVisible,
+            non_scroller_client_area->GetComputedStyle()->OverflowY());
+
+  // Only UA selectors can match these pseudo-elements,
+  // backface-visibility should be unchanged.
+  EXPECT_EQ(EBackfaceVisibility::kVisible,
+            overscroll_parent_foo->GetComputedStyle()->BackfaceVisibility());
+  EXPECT_EQ(EBackfaceVisibility::kVisible,
+            scroller_client_area->GetComputedStyle()->BackfaceVisibility());
+}
+
 TEST_F(ElementTest, GenerateScrollMarkerGroup) {
   GetDocument().body()->SetInnerHTMLWithoutTrustedTypes(R"HTML(
     <style id="test-style">
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index 6566e16..7d9e8c9 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -2289,7 +2289,6 @@
     : WebNavigationControl(scope, frame_token),
       client_(client),
       local_frame_client_(MakeGarbageCollected<LocalFrameClientImpl>(this)),
-      autofill_client_(nullptr),
       find_in_page_(
           MakeGarbageCollected<FindInPage>(*this, interface_registry)),
       interface_registry_(interface_registry),
@@ -3266,6 +3265,9 @@
   if (frame_widget_) {
     frame_widget_->OnDevToolsSessionConnectionChanged(attached);
   }
+  if (autofill_client_) {
+    autofill_client_->OnDevToolsSessionConnectionChanged(attached);
+  }
 }
 
 void WebLocalFrameImpl::WasHidden() {
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.h b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
index a2850434..ae5f777 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.h
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
@@ -687,7 +687,7 @@
 
   Member<WebDevToolsAgentImpl> dev_tools_agent_;
 
-  WebAutofillClient* autofill_client_;
+  WebAutofillClient* autofill_client_ = nullptr;
 
   WebContentCaptureClient* content_capture_client_ = nullptr;
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_highlight.cc b/third_party/blink/renderer/core/inspector/inspector_highlight.cc
index c956978..dbcad9c6 100644
--- a/third_party/blink/renderer/core/inspector/inspector_highlight.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_highlight.cc
@@ -34,7 +34,7 @@
 #include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/core/layout/logical_box_fragment.h"
-#include "third_party/blink/renderer/core/layout/masonry/layout_masonry.h"
+#include "third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.h"
 #include "third_party/blink/renderer/core/layout/physical_box_fragment.h"
 #include "third_party/blink/renderer/core/layout/shapes/shape_outside_info.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
@@ -811,7 +811,7 @@
       layout_object->IsLayoutGrid()
           ? To<LayoutGrid>(layout_object)
                 ->ExplicitGridStartForDirection(direction)
-          : To<LayoutMasonry>(layout_object)
+          : To<LayoutGridLanes>(layout_object)
                 ->ExplicitGridStartForDirection(direction);
   // Go line by line, calculating the offset to fall in the middle of gaps
   // if needed.
@@ -877,7 +877,7 @@
       layout_object->IsLayoutGrid()
           ? To<LayoutGrid>(layout_object)
                 ->ExplicitGridEndForDirection(direction)
-          : To<LayoutMasonry>(layout_object)
+          : To<LayoutGridLanes>(layout_object)
                 ->ExplicitGridEndForDirection(direction);
 
   {
@@ -1007,7 +1007,7 @@
     GridTrackSizingDirection direction,
     const Vector<LayoutUnit>& masonry_tracks,
     bool is_for_columns) {
-  const auto* masonry = To<LayoutMasonry>(node->GetLayoutObject());
+  const auto* masonry = To<LayoutGridLanes>(node->GetLayoutObject());
   std::unique_ptr<protocol::DictionaryValue> area_paths =
       protocol::DictionaryValue::create();
 
@@ -1162,7 +1162,7 @@
     float scale,
     const Vector<LayoutUnit>& positions,
     LayoutUnit alt_axis_pos) {
-  auto* masonry = To<LayoutMasonry>(node->GetLayoutObject());
+  auto* masonry = To<LayoutGridLanes>(node->GetLayoutObject());
   const bool is_rtl = (direction == kForColumns) &&
                       !masonry->StyleRef().IsLeftToRightDirection();
   const LayoutUnit gap = masonry->GridGap(direction);
@@ -1559,20 +1559,20 @@
     const InspectorGridHighlightConfig& grid_highlight_config,
     float scale) {
   LocalFrameView* containing_view = element->GetDocument().View();
-  auto* masonry = To<LayoutMasonry>(element->GetLayoutObject());
+  auto* masonry = To<LayoutGridLanes>(element->GetLayoutObject());
   std::unique_ptr<protocol::DictionaryValue> grid_info =
       protocol::DictionaryValue::create();
 
   grid_info->setInteger("rotationAngle", GetRotationAngle(masonry));
   grid_info->setString("writingMode", GetWritingMode(masonry->StyleRef()));
   const bool is_for_columns =
-      masonry->StyleRef().MasonryTrackSizingDirection() == kForColumns;
+      masonry->StyleRef().GridLanesTrackSizingDirection() == kForColumns;
 
   const Vector<LayoutUnit> masonry_tracks =
       masonry->GridTrackPositions(is_for_columns ? kForColumns : kForRows);
   const LayoutUnit gap =
       masonry->GridGap(is_for_columns ? kForColumns : kForRows) +
-      masonry->MasonryItemOffset(is_for_columns ? kForColumns : kForRows);
+      masonry->GridLanesItemOffset(is_for_columns ? kForColumns : kForRows);
   const LayoutUnit span_start =
       is_for_columns ? masonry->ContentTop() : masonry->ContentLeft();
   const LayoutUnit span_size =
diff --git a/third_party/blink/renderer/core/layout/build.gni b/third_party/blink/renderer/core/layout/build.gni
index b2ee6c6..79fff07f 100644
--- a/third_party/blink/renderer/core/layout/build.gni
+++ b/third_party/blink/renderer/core/layout/build.gni
@@ -433,12 +433,12 @@
   "logical_fragment.h",
   "logical_fragment_link.h",
   "map_coordinates_flags.h",
-  "masonry/layout_masonry.cc",
-  "masonry/layout_masonry.h",
+  "masonry/grid_lanes_node.cc",
+  "masonry/grid_lanes_node.h",
+  "masonry/layout_grid_lanes.cc",
+  "masonry/layout_grid_lanes.h",
   "masonry/masonry_layout_algorithm.cc",
   "masonry/masonry_layout_algorithm.h",
-  "masonry/masonry_node.cc",
-  "masonry/masonry_node.h",
   "masonry/masonry_running_positions.cc",
   "masonry/masonry_running_positions.h",
   "masonry/masonry_item_group.h",
diff --git a/third_party/blink/renderer/core/layout/grid/grid_item.cc b/third_party/blink/renderer/core/layout/grid/grid_item.cc
index 06dc195..02a5a07 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_item.cc
+++ b/third_party/blink/renderer/core/layout/grid/grid_item.cc
@@ -58,7 +58,7 @@
     //
     // TODO(almaher): Update alignment logic if needed once we resolve on
     // https://github.com/w3c/csswg-drafts/issues/10275.
-    return parent_grid_style.MasonryTrackSizingDirection() == track_direction;
+    return parent_grid_style.GridLanesTrackSizingDirection() == track_direction;
   })();
 
   // Auto-margins take precedence over any alignment properties.
diff --git a/third_party/blink/renderer/core/layout/grid/grid_line_resolver.cc b/third_party/blink/renderer/core/layout/grid/grid_line_resolver.cc
index bc7d465..ef68465 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_line_resolver.cc
+++ b/third_party/blink/renderer/core/layout/grid/grid_line_resolver.cc
@@ -33,7 +33,7 @@
     : style_(&parent_style) {
   DCHECK(parent_style.IsDisplayGridLanesBox());
 
-  (parent_style.MasonryTrackSizingDirection() == kForColumns)
+  (parent_style.GridLanesTrackSizingDirection() == kForColumns)
       ? column_auto_repetitions_ = auto_repetitions
       : row_auto_repetitions_ = auto_repetitions;
 }
diff --git a/third_party/blink/renderer/core/layout/grid/layout_grid.h b/third_party/blink/renderer/core/layout/grid/layout_grid.h
index 1d44fc7..5c6ec40 100644
--- a/third_party/blink/renderer/core/layout/grid/layout_grid.h
+++ b/third_party/blink/renderer/core/layout/grid/layout_grid.h
@@ -34,7 +34,7 @@
   static Vector<LayoutUnit> ComputeExpandedPositions(
       const GridLayoutTrackCollection& track_collection);
 
-  // Helper functions shared between LayoutGrid and LayoutMasonry.
+  // Helper functions shared between LayoutGrid and LayoutGridLanes.
   static const GridLayoutData* GetGridLayoutDataFromFragments(
       const LayoutBlock* layout_block);
   static LayoutUnit ComputeGridGap(const GridLayoutData* grid_layout_data,
diff --git a/third_party/blink/renderer/core/layout/layout_block.cc b/third_party/blink/renderer/core/layout/layout_block.cc
index 421718d6..6b5683a 100644
--- a/third_party/blink/renderer/core/layout/layout_block.cc
+++ b/third_party/blink/renderer/core/layout/layout_block.cc
@@ -56,7 +56,7 @@
 #include "third_party/blink/renderer/core/layout/layout_theme.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/core/layout/length_utils.h"
-#include "third_party/blink/renderer/core/layout/masonry/layout_masonry.h"
+#include "third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.h"
 #include "third_party/blink/renderer/core/layout/mathml/layout_mathml_block.h"
 #include "third_party/blink/renderer/core/layout/physical_box_fragment.h"
 #include "third_party/blink/renderer/core/layout/svg/layout_svg_text.h"
@@ -735,7 +735,7 @@
   } else if (new_display == EDisplay::kGrid) {
     layout_block = MakeGarbageCollected<LayoutGrid>(/*element=*/nullptr);
   } else if (new_display == EDisplay::kGridLanes) {
-    layout_block = MakeGarbageCollected<LayoutMasonry>(/*element=*/nullptr);
+    layout_block = MakeGarbageCollected<LayoutGridLanes>(/*element=*/nullptr);
   } else if (new_display == EDisplay::kBlockMath) {
     layout_block = MakeGarbageCollected<LayoutMathMLBlock>(/*element=*/nullptr);
   } else {
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 6823042e..dbc2653 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -106,7 +106,7 @@
 #include "third_party/blink/renderer/core/layout/list/layout_inside_list_marker.h"
 #include "third_party/blink/renderer/core/layout/list/layout_list_item.h"
 #include "third_party/blink/renderer/core/layout/list/layout_outside_list_marker.h"
-#include "third_party/blink/renderer/core/layout/masonry/layout_masonry.h"
+#include "third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.h"
 #include "third_party/blink/renderer/core/layout/mathml/layout_mathml_block.h"
 #include "third_party/blink/renderer/core/layout/physical_box_fragment.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_layout_info.h"
@@ -411,7 +411,7 @@
     case EDisplay::kGridLanes:
     case EDisplay::kInlineGridLanes:
       // TODO(almaher): Add use counter for CSS Grid Lanes.
-      return MakeGarbageCollected<LayoutMasonry>(element);
+      return MakeGarbageCollected<LayoutGridLanes>(element);
     case EDisplay::kMath:
     case EDisplay::kBlockMath:
       return MakeGarbageCollected<LayoutMathMLBlock>(element);
diff --git a/third_party/blink/renderer/core/layout/length_utils.cc b/third_party/blink/renderer/core/layout/length_utils.cc
index 54c40ab..e82f236 100644
--- a/third_party/blink/renderer/core/layout/length_utils.cc
+++ b/third_party/blink/renderer/core/layout/length_utils.cc
@@ -1394,7 +1394,7 @@
 LayoutUnit ResolveItemToleranceForMasonry(const ComputedStyle& style,
                                           const LogicalSize& available_size) {
   return ResolveItemToleranceLength(
-             style, (style.MasonryTrackSizingDirection() == kForColumns)
+             style, (style.GridLanesTrackSizingDirection() == kForColumns)
                         ? available_size.block_size
                         : available_size.inline_size)
       .value_or(LayoutUnit(style.GetFontDescription().ComputedPixelSize()));
diff --git a/third_party/blink/renderer/core/layout/masonry/masonry_node.cc b/third_party/blink/renderer/core/layout/masonry/grid_lanes_node.cc
similarity index 64%
rename from third_party/blink/renderer/core/layout/masonry/masonry_node.cc
rename to third_party/blink/renderer/core/layout/masonry/grid_lanes_node.cc
index a0f2fdf..9d5bfb9 100644
--- a/third_party/blink/renderer/core/layout/masonry/masonry_node.cc
+++ b/third_party/blink/renderer/core/layout/masonry/grid_lanes_node.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be found
 // in the LICENSE file.
 
-#include "third_party/blink/renderer/core/layout/masonry/masonry_node.h"
+#include "third_party/blink/renderer/core/layout/masonry/grid_lanes_node.h"
 
 #include "third_party/blink/renderer/core/layout/grid/grid_item.h"
 #include "third_party/blink/renderer/core/layout/grid/grid_line_resolver.h"
@@ -11,39 +11,40 @@
 
 namespace {
 
-void AdjustMasonryItemSpan(GridItemData& masonry_item,
-                           const GridLineResolver& line_resolver,
-                           const GridTrackSizingDirection grid_axis_direction) {
+void AdjustGridLanesItemSpan(
+    GridItemData& grid_lanes_item,
+    const GridLineResolver& line_resolver,
+    const GridTrackSizingDirection grid_axis_direction) {
   // Resolve the positions of the items based on style. We can only resolve
   // the number of spans for each item based on the grid axis.
   GridSpan item_span = line_resolver.ResolveGridPositionsFromStyle(
-      masonry_item.node.Style(), grid_axis_direction);
+      grid_lanes_item.node.Style(), grid_axis_direction);
 
   if (item_span.IsIndefinite()) {
-    masonry_item.is_auto_placed = true;
+    grid_lanes_item.is_auto_placed = true;
   }
 
-  masonry_item.resolved_position.SetSpan(item_span, grid_axis_direction);
+  grid_lanes_item.resolved_position.SetSpan(item_span, grid_axis_direction);
 }
 
 }  // namespace
 
-MasonryItemGroups MasonryNode::CollectItemGroups(
+MasonryItemGroups GridLanesNode::CollectItemGroups(
     const GridLineResolver& line_resolver,
-    const GridItems& masonry_items,
+    const GridItems& grid_lanes_items,
     wtf_size_t& max_end_line,
     wtf_size_t& start_offset,
     wtf_size_t& unplaced_item_span_count) const {
-  const auto grid_axis_direction = Style().MasonryTrackSizingDirection();
+  const auto grid_axis_direction = Style().GridLanesTrackSizingDirection();
 
   start_offset = 0;
   MasonryItemGroupMap item_group_map;
 
-  for (wtf_size_t index = 0; index < masonry_items.Size(); ++index) {
-    const Member<GridItemData>& masonry_item = masonry_items[index];
-    DCHECK(masonry_item);
+  for (wtf_size_t index = 0; index < grid_lanes_items.Size(); ++index) {
+    const Member<GridItemData>& grid_lanes_item = grid_lanes_items[index];
+    DCHECK(grid_lanes_item);
 
-    const BlockNode& child = masonry_item->node;
+    const BlockNode& child = grid_lanes_item->node;
     if (child.IsOutOfFlowPositioned()) {
       continue;
     }
@@ -54,7 +55,7 @@
 
     const auto& item_span = item_properties.Span();
     // Keep a running sum of unplaced item spans to determine where to
-    // place auto placed virtual items per the auto-fit masonry heuristic.
+    // place auto placed virtual items per the auto-fit grid-lanes heuristic.
     //
     // https://drafts.csswg.org/css-grid-3/#repeat-auto-fit
     if (item_span.IsIndefinite()) {
@@ -68,9 +69,9 @@
     const auto group_it = item_group_map.find(item_properties);
     if (group_it == item_group_map.end()) {
       item_group_map.insert(item_properties,
-                            GridItems::GridItemDataVector({masonry_item}));
+                            GridItems::GridItemDataVector({grid_lanes_item}));
     } else {
-      group_it->value.emplace_back(masonry_item);
+      group_it->value.emplace_back(grid_lanes_item);
     }
   }
 
@@ -96,16 +97,16 @@
   return item_groups;
 }
 
-GridItems MasonryNode::ConstructMasonryItems(
+GridItems GridLanesNode::ConstructGridLanesItems(
     const GridLineResolver& line_resolver,
     HeapVector<Member<LayoutBox>>* opt_oof_children) const {
   const ComputedStyle& style = Style();
   const GridTrackSizingDirection grid_axis_direction =
-      style.MasonryTrackSizingDirection();
+      style.GridLanesTrackSizingDirection();
 
-  GridItems masonry_items;
+  GridItems grid_lanes_items;
   {
-    bool should_sort_masonry_items_by_order_property = false;
+    bool should_sort_grid_lanes_items_by_order_property = false;
     const int initial_order = ComputedStyleInitialValues::InitialOrder();
 
     // This collects all our children, and orders them by their order property.
@@ -117,32 +118,34 @@
         continue;
       }
 
-      GridItemData* masonry_item = MakeGarbageCollected<GridItemData>(
+      GridItemData* grid_lanes_item = MakeGarbageCollected<GridItemData>(
           To<BlockNode>(child), /*parent_style=*/style);
 
       // We'll need to sort when we encounter a non-initial order property.
-      should_sort_masonry_items_by_order_property |=
+      should_sort_grid_lanes_items_by_order_property |=
           child.Style().Order() != initial_order;
 
-      AdjustMasonryItemSpan(*masonry_item, line_resolver, grid_axis_direction);
-      masonry_items.Append(masonry_item);
+      AdjustGridLanesItemSpan(*grid_lanes_item, line_resolver,
+                              grid_axis_direction);
+      grid_lanes_items.Append(grid_lanes_item);
     }
 
     // Sort items by order property if needed.
-    if (should_sort_masonry_items_by_order_property) {
-      masonry_items.SortByOrderProperty();
+    if (should_sort_grid_lanes_items_by_order_property) {
+      grid_lanes_items.SortByOrderProperty();
     }
   }
-  return masonry_items;
+  return grid_lanes_items;
 }
 
-void MasonryNode::AdjustMasonryItemSpans(
-    GridItems& masonry_items,
+void GridLanesNode::AdjustGridLanesItemSpans(
+    GridItems& grid_lanes_items,
     const GridLineResolver& line_resolver) const {
   const GridTrackSizingDirection grid_axis_direction =
-      Style().MasonryTrackSizingDirection();
-  for (GridItemData& masonry_item : masonry_items) {
-    AdjustMasonryItemSpan(masonry_item, line_resolver, grid_axis_direction);
+      Style().GridLanesTrackSizingDirection();
+  for (GridItemData& grid_lanes_item : grid_lanes_items) {
+    AdjustGridLanesItemSpan(grid_lanes_item, line_resolver,
+                            grid_axis_direction);
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/masonry/masonry_node.h b/third_party/blink/renderer/core/layout/masonry/grid_lanes_node.h
similarity index 67%
rename from third_party/blink/renderer/core/layout/masonry/masonry_node.h
rename to third_party/blink/renderer/core/layout/masonry/grid_lanes_node.h
index 31994c2a..8df3796 100644
--- a/third_party/blink/renderer/core/layout/masonry/masonry_node.h
+++ b/third_party/blink/renderer/core/layout/masonry/grid_lanes_node.h
@@ -2,12 +2,12 @@
 // 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_CORE_LAYOUT_MASONRY_MASONRY_NODE_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_MASONRY_MASONRY_NODE_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_MASONRY_GRID_LANES_NODE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_MASONRY_GRID_LANES_NODE_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/block_node.h"
-#include "third_party/blink/renderer/core/layout/masonry/layout_masonry.h"
+#include "third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.h"
 #include "third_party/blink/renderer/core/layout/masonry/masonry_item_group.h"
 
 namespace blink {
@@ -15,45 +15,45 @@
 class GridItems;
 class GridLineResolver;
 
-// Masonry specific extensions to `BlockNode`.
-class CORE_EXPORT MasonryNode final : public BlockNode {
+// Grid Lanes specific extensions to `BlockNode`.
+class CORE_EXPORT GridLanesNode final : public BlockNode {
  public:
-  explicit MasonryNode(LayoutBox* box) : BlockNode(box) {
+  explicit GridLanesNode(LayoutBox* box) : BlockNode(box) {
     DCHECK(box);
     DCHECK(box->IsLayoutGridLanes());
   }
 
   const GridPlacementData& CachedPlacementData() const {
-    return To<LayoutMasonry>(box_.Get())->CachedPlacementData();
+    return To<LayoutGridLanes>(box_.Get())->CachedPlacementData();
   }
 
   // Collects the children of this node (using the `GridItemData` for each child
-  // provided by `masonry_items`) into item groups based on their placement,
+  // provided by `grid_lanes_items`) into item groups based on their placement,
   // span size, and baseline-sharing group. `start_offset` calculates the offset
   // of the first grid line in the implicit grid, which is used to translate
   // definite grid spans to a 0-indexed format. `unplaced_item_span_count` is
   // an ouput param that is the sum of all auto placed item span sizes.
   MasonryItemGroups CollectItemGroups(
       const GridLineResolver& line_resolver,
-      const GridItems& masonry_items,
+      const GridItems& grid_lanes_items,
       wtf_size_t& max_end_line,
       wtf_size_t& start_offset,
       wtf_size_t& unplaced_item_span_count) const;
 
   // Collects the children of this node, sorts by order property if needed, and
   // resolves the grid line positions of the items based on style.
-  GridItems ConstructMasonryItems(
+  GridItems ConstructGridLanesItems(
       const GridLineResolver& line_resolver,
       HeapVector<Member<LayoutBox>>* opt_oof_children = nullptr) const;
 
   // Update the grid line positions of the items based on style and provided
   // `line_resolver`.
-  void AdjustMasonryItemSpans(GridItems& masonry_items,
-                              const GridLineResolver& line_resolver) const;
+  void AdjustGridLanesItemSpans(GridItems& grid_lanes_items,
+                                const GridLineResolver& line_resolver) const;
 };
 
 template <>
-struct DowncastTraits<MasonryNode> {
+struct DowncastTraits<GridLanesNode> {
   static bool AllowFrom(const LayoutInputNode& node) {
     return node.IsGridLanes();
   }
@@ -61,4 +61,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_MASONRY_MASONRY_NODE_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_MASONRY_GRID_LANES_NODE_H_
diff --git a/third_party/blink/renderer/core/layout/masonry/layout_masonry.cc b/third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.cc
similarity index 68%
rename from third_party/blink/renderer/core/layout/masonry/layout_masonry.cc
rename to third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.cc
index d4e8971..4411be6 100644
--- a/third_party/blink/renderer/core/layout/masonry/layout_masonry.cc
+++ b/third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.cc
@@ -2,22 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be found
 // in the LICENSE file.
 
-#include "third_party/blink/renderer/core/layout/masonry/layout_masonry.h"
+#include "third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.h"
 
 #include "third_party/blink/renderer/core/layout/grid/layout_grid.h"
 
 namespace blink {
 
-LayoutMasonry::LayoutMasonry(Element* element) : LayoutBlock(element) {}
+LayoutGridLanes::LayoutGridLanes(Element* element) : LayoutBlock(element) {}
 
-const GridLayoutData* LayoutMasonry::LayoutData() const {
+const GridLayoutData* LayoutGridLanes::LayoutData() const {
   return LayoutGrid::GetGridLayoutDataFromFragments(this);
 }
 
-Vector<LayoutUnit> LayoutMasonry::GridTrackPositions(
+Vector<LayoutUnit> LayoutGridLanes::GridTrackPositions(
     GridTrackSizingDirection track_direction) const {
   NOT_DESTROYED();
-  if (track_direction != StyleRef().MasonryTrackSizingDirection()) {
+  if (track_direction != StyleRef().GridLanesTrackSizingDirection()) {
     return {};
   }
   return LayoutGrid::ComputeExpandedPositions(track_direction == kForColumns
@@ -25,35 +25,36 @@
                                                   : LayoutData()->Rows());
 }
 
-LayoutUnit LayoutMasonry::GridGap(
+LayoutUnit LayoutGridLanes::GridGap(
     GridTrackSizingDirection track_direction) const {
   NOT_DESTROYED();
   return LayoutGrid::ComputeGridGap(LayoutData(), track_direction);
 }
 
-LayoutUnit LayoutMasonry::MasonryItemOffset(
+LayoutUnit LayoutGridLanes::GridLanesItemOffset(
     GridTrackSizingDirection track_direction) const {
   NOT_DESTROYED();
-  // Distribution offset is baked into the `gutter_size` in Masonry.
+  // Distribution offset is baked into the `gutter_size` in Grid Lanes.
   return LayoutUnit();
 }
 
-bool LayoutMasonry::HasCachedPlacementData() const {
+bool LayoutGridLanes::HasCachedPlacementData() const {
   // TODO(almaher): Check for !IsGridPlacementDirty() similar to
   // LayoutGrid.
   return !!cached_placement_data_;
 }
 
-const GridPlacementData& LayoutMasonry::CachedPlacementData() const {
+const GridPlacementData& LayoutGridLanes::CachedPlacementData() const {
   DCHECK(cached_placement_data_);
   return *cached_placement_data_;
 }
 
-void LayoutMasonry::SetCachedPlacementData(GridPlacementData&& placement_data) {
+void LayoutGridLanes::SetCachedPlacementData(
+    GridPlacementData&& placement_data) {
   cached_placement_data_ = std::move(placement_data);
 }
 
-wtf_size_t LayoutMasonry::AutoRepeatCountForDirection(
+wtf_size_t LayoutGridLanes::AutoRepeatCountForDirection(
     GridTrackSizingDirection track_direction) const {
   NOT_DESTROYED();
   if (!cached_placement_data_) {
@@ -62,7 +63,7 @@
   return cached_placement_data_->AutoRepeatTrackCount(track_direction);
 }
 
-wtf_size_t LayoutMasonry::ExplicitGridStartForDirection(
+wtf_size_t LayoutGridLanes::ExplicitGridStartForDirection(
     GridTrackSizingDirection track_direction) const {
   NOT_DESTROYED();
   if (!cached_placement_data_) {
@@ -71,7 +72,7 @@
   return cached_placement_data_->StartOffset(track_direction);
 }
 
-wtf_size_t LayoutMasonry::ExplicitGridEndForDirection(
+wtf_size_t LayoutGridLanes::ExplicitGridEndForDirection(
     GridTrackSizingDirection track_direction) const {
   NOT_DESTROYED();
   if (!cached_placement_data_) {
@@ -83,10 +84,10 @@
       cached_placement_data_->ExplicitGridTrackCount(track_direction));
 }
 
-Vector<LayoutUnit, 1> LayoutMasonry::TrackSizesForComputedStyle(
+Vector<LayoutUnit, 1> LayoutGridLanes::TrackSizesForComputedStyle(
     GridTrackSizingDirection track_direction) const {
   NOT_DESTROYED();
-  if (track_direction != StyleRef().MasonryTrackSizingDirection()) {
+  if (track_direction != StyleRef().GridLanesTrackSizingDirection()) {
     return {};
   }
   return LayoutGrid::CollectTrackSizesForComputedStyle(LayoutData(),
diff --git a/third_party/blink/renderer/core/layout/masonry/layout_masonry.h b/third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.h
similarity index 80%
rename from third_party/blink/renderer/core/layout/masonry/layout_masonry.h
rename to third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.h
index a7b6b0c..164a060 100644
--- a/third_party/blink/renderer/core/layout/masonry/layout_masonry.h
+++ b/third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be found
 // in the LICENSE file.
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_MASONRY_LAYOUT_MASONRY_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_MASONRY_LAYOUT_MASONRY_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_MASONRY_LAYOUT_GRID_LANES_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_MASONRY_LAYOUT_GRID_LANES_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/grid/grid_data.h"
@@ -13,15 +13,15 @@
 
 namespace blink {
 
-class CORE_EXPORT LayoutMasonry : public LayoutBlock {
+class CORE_EXPORT LayoutGridLanes : public LayoutBlock {
  public:
-  explicit LayoutMasonry(Element* element);
+  explicit LayoutGridLanes(Element* element);
 
   const char* GetName() const override {
     NOT_DESTROYED();
     // This string can affect a production behavior.
     // See tool_highlight.ts in devtools-frontend.
-    return "LayoutMasonry";
+    return "LayoutGridLanes";
   }
 
   bool HasCachedPlacementData() const;
@@ -38,7 +38,8 @@
   wtf_size_t ExplicitGridEndForDirection(
       GridTrackSizingDirection direction) const;
   LayoutUnit GridGap(GridTrackSizingDirection track_direction) const;
-  LayoutUnit MasonryItemOffset(GridTrackSizingDirection track_direction) const;
+  LayoutUnit GridLanesItemOffset(
+      GridTrackSizingDirection track_direction) const;
   Vector<LayoutUnit, 1> TrackSizesForComputedStyle(
       GridTrackSizingDirection track_direction) const;
 
@@ -56,14 +57,14 @@
   // TODO(almaher): Do we need special overrides of AddChild(),
   // RemoveChild(), StyleDidChange(), MarkGridDirty() etc?
 
-  // Caches masonry placement data for DevTools inspector highlighting.
+  // Caches grid-lanes placement data for DevTools inspector highlighting.
   // This avoids recomputing during inspector queries.
   std::optional<GridPlacementData> cached_placement_data_;
 };
 
 // wtf/casting.h helper.
 template <>
-struct DowncastTraits<LayoutMasonry> {
+struct DowncastTraits<LayoutGridLanes> {
   static bool AllowFrom(const LayoutObject& object) {
     return object.IsLayoutGridLanes();
   }
@@ -71,4 +72,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_MASONRY_LAYOUT_MASONRY_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_MASONRY_LAYOUT_GRID_LANES_H_
diff --git a/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.cc b/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.cc
index 39fdacb..c5e806e 100644
--- a/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.cc
@@ -14,7 +14,7 @@
 #include "third_party/blink/renderer/core/layout/grid/grid_track_sizing_algorithm.h"
 #include "third_party/blink/renderer/core/layout/layout_utils.h"
 #include "third_party/blink/renderer/core/layout/logical_box_fragment.h"
-#include "third_party/blink/renderer/core/layout/masonry/layout_masonry.h"
+#include "third_party/blink/renderer/core/layout/masonry/layout_grid_lanes.h"
 #include "third_party/blink/renderer/core/layout/masonry/masonry_running_positions.h"
 
 namespace blink {
@@ -41,7 +41,7 @@
     const MinMaxSizesFloatInput&) {
   const ComputedStyle& style = Style();
   const bool is_for_columns =
-      style.MasonryTrackSizingDirection() == kForColumns;
+      style.GridLanesTrackSizingDirection() == kForColumns;
 
   auto ComputeIntrinsicInlineSize = [&](SizingConstraint sizing_constraint) {
     bool needs_intrinsic_track_size = false;
@@ -570,7 +570,7 @@
     const wtf_size_t auto_repetition_count,
     wtf_size_t& start_offset) const {
   const auto& style = Style();
-  const auto grid_axis_direction = style.MasonryTrackSizingDirection();
+  const auto grid_axis_direction = style.GridLanesTrackSizingDirection();
   const bool is_for_columns = grid_axis_direction == kForColumns;
 
   const LayoutUnit grid_axis_gap =
@@ -901,14 +901,15 @@
                                            needs_intrinsic_track_size));
   const auto& node = Node();
   if (masonry_items.IsEmpty()) {
-    masonry_items = node.ConstructMasonryItems(line_resolver, opt_oof_children);
+    masonry_items =
+        node.ConstructGridLanesItems(line_resolver, opt_oof_children);
   } else {
     // If `masonry_items` is not empty, that means that we are in
     // a second track sizing pass required for intrinsic tracks within
     // a repeat() track definition. Don't construct the masonry items
     // from scratch. Rather, adjust their spans based on the updated
     // `line_resolver`.
-    node.AdjustMasonryItemSpans(masonry_items, line_resolver);
+    node.AdjustGridLanesItemSpans(masonry_items, line_resolver);
   }
 
   return BuildGridAxisTracks(line_resolver, masonry_items, sizing_constraint,
@@ -924,7 +925,7 @@
     Vector<wtf_size_t>& collapsed_track_indexes,
     wtf_size_t& start_offset) const {
   const auto& style = Style();
-  const auto grid_axis_direction = style.MasonryTrackSizingDirection();
+  const auto grid_axis_direction = style.GridLanesTrackSizingDirection();
   GridItems virtual_items = BuildVirtualMasonryItems(
       line_resolver, masonry_items, needs_intrinsic_track_size,
       sizing_constraint, line_resolver.AutoRepetitions(grid_axis_direction),
@@ -938,7 +939,7 @@
     } else {
       placement_data.row_start_offset = start_offset;
     }
-    To<LayoutMasonry>(Node().GetLayoutBox())
+    To<LayoutGridLanes>(Node().GetLayoutBox())
         ->SetCachedPlacementData(std::move(placement_data));
   }
 
@@ -1003,7 +1004,7 @@
   CHECK_NE(track_collection.GetIntrinsicSizedRepeaterSetIndex(), kNotFound);
   const ComputedStyle& style = Style();
   const bool is_for_columns =
-      style.MasonryTrackSizingDirection() == kForColumns;
+      style.GridLanesTrackSizingDirection() == kForColumns;
 
   const GridTrackList& track_list =
       is_for_columns ? style.GridTemplateColumns().GetTrackList()
@@ -1043,9 +1044,9 @@
     const Vector<LayoutUnit>* intrinsic_repeat_track_sizes,
     bool& needs_intrinsic_track_size) const {
   const ComputedStyle& style = Style();
-  GridTrackSizingDirection masonry_track_sizing_direction =
-      style.MasonryTrackSizingDirection();
-  const bool is_for_columns = masonry_track_sizing_direction == kForColumns;
+  GridTrackSizingDirection grid_axis_direction =
+      style.GridLanesTrackSizingDirection();
+  const bool is_for_columns = grid_axis_direction == kForColumns;
 
   const GridTrackList& track_list =
       is_for_columns ? style.GridTemplateColumns().GetTrackList()
@@ -1073,7 +1074,7 @@
   // grid_layout_utils.cc.
 
   const LayoutUnit gutter_size = GridTrackSizingAlgorithm::CalculateGutterSize(
-      style, masonry_available_size_, masonry_track_sizing_direction);
+      style, masonry_available_size_, grid_axis_direction);
 
   return CalculateAutomaticRepetitions(
       track_list, gutter_size,
@@ -1184,7 +1185,7 @@
     bool is_for_min_max_sizing) const {
   LogicalSize containing_size = masonry_available_size_;
   const auto writing_mode = GetConstraintSpace().GetWritingMode();
-  const auto grid_axis_direction = Style().MasonryTrackSizingDirection();
+  const auto grid_axis_direction = Style().GridLanesTrackSizingDirection();
   const bool is_parallel_with_root_grid =
       masonry_item.is_parallel_with_root_grid;
 
@@ -1251,7 +1252,7 @@
     GridItemData* out_of_flow_item) {
   DCHECK(out_of_flow_item && out_of_flow_item->IsOutOfFlow());
   const bool is_for_columns =
-      masonry_style.MasonryTrackSizingDirection() == kForColumns;
+      masonry_style.GridLanesTrackSizingDirection() == kForColumns;
 
   out_of_flow_item->ComputeOutOfFlowItemPlacement(
       is_for_columns ? layout_data.Columns() : layout_data.Rows(),
diff --git a/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.h b/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.h
index 3c3ee3c..4b348b71 100644
--- a/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.h
@@ -8,7 +8,7 @@
 #include "third_party/blink/renderer/core/layout/block_break_token.h"
 #include "third_party/blink/renderer/core/layout/box_fragment_builder.h"
 #include "third_party/blink/renderer/core/layout/layout_algorithm.h"
-#include "third_party/blink/renderer/core/layout/masonry/masonry_node.h"
+#include "third_party/blink/renderer/core/layout/masonry/grid_lanes_node.h"
 
 namespace blink {
 
@@ -25,7 +25,9 @@
 struct GridPlacementData;
 
 class CORE_EXPORT MasonryLayoutAlgorithm
-    : public LayoutAlgorithm<MasonryNode, BoxFragmentBuilder, BlockBreakToken> {
+    : public LayoutAlgorithm<GridLanesNode,
+                             BoxFragmentBuilder,
+                             BlockBreakToken> {
  public:
   explicit MasonryLayoutAlgorithm(const LayoutAlgorithmParams& params);
 
diff --git a/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm_test.cc b/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm_test.cc
index 15c7849..9b17f61 100644
--- a/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm_test.cc
+++ b/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm_test.cc
@@ -27,11 +27,12 @@
     const GridLineResolver line_resolver(style, /*auto_repetitions=*/0);
     collapsed_track_indexes_.clear();
 
-    auto masonry_items = algorithm.Node().ConstructMasonryItems(line_resolver);
+    auto grid_lanes_items =
+        algorithm.Node().ConstructGridLanesItems(line_resolver);
     bool needs_intrinsic_track_size = false;
     grid_axis_tracks_ = algorithm.ComputeGridAxisTracks(
         SizingConstraint::kLayout, /*intrinsic_repeat_track_sizes=*/nullptr,
-        masonry_items, collapsed_track_indexes_, start_offset,
+        grid_lanes_items, collapsed_track_indexes_, start_offset,
         needs_intrinsic_track_size);
 
     // We have a repeat() track definition with an intrinsic sized track(s). The
@@ -45,19 +46,19 @@
       CHECK(collapsed_track_indexes_.empty());
 
       Vector<LayoutUnit> intrinsic_repeat_track_sizes =
-          algorithm.GetIntrinsicRepeaterTrackSizes(!masonry_items.IsEmpty(),
+          algorithm.GetIntrinsicRepeaterTrackSizes(!grid_lanes_items.IsEmpty(),
                                                    grid_axis_tracks_.value());
       grid_axis_tracks_ = algorithm.ComputeGridAxisTracks(
           SizingConstraint::kLayout, &intrinsic_repeat_track_sizes,
-          masonry_items, collapsed_track_indexes_, start_offset,
+          grid_lanes_items, collapsed_track_indexes_, start_offset,
           needs_intrinsic_track_size);
     }
 
     const auto grid_axis_direction = grid_axis_tracks_->Direction();
-    ASSERT_EQ(grid_axis_direction, style.MasonryTrackSizingDirection());
+    ASSERT_EQ(grid_axis_direction, style.GridLanesTrackSizingDirection());
 
     for (const auto& masonry_item : algorithm.BuildVirtualMasonryItems(
-             line_resolver, masonry_items, needs_intrinsic_track_size,
+             line_resolver, grid_lanes_items, needs_intrinsic_track_size,
              SizingConstraint::kLayout,
              line_resolver.AutoRepetitions(grid_axis_direction),
              start_offset)) {
@@ -142,7 +143,7 @@
   Vector<wtf_size_t> collapsed_track_indexes_;
 };
 
-TEST_F(MasonryLayoutAlgorithmTest, ConstructMasonryItems) {
+TEST_F(MasonryLayoutAlgorithmTest, ConstructGridLanesItems) {
   SetBodyInnerHTML(R"HTML(
     <style>
     #grid-lanes {
@@ -162,10 +163,10 @@
     </div>
   )HTML");
 
-  MasonryNode node(GetLayoutBoxByElementId("grid-lanes"));
+  GridLanesNode node(GetLayoutBoxByElementId("grid-lanes"));
 
   const GridLineResolver line_resolver(node.Style(), /*auto_repetitions=*/0);
-  auto masonry_items = node.ConstructMasonryItems(line_resolver);
+  auto grid_lanes_items = node.ConstructGridLanesItems(line_resolver);
 
   const Vector<GridSpan> expected_spans = {
       GridSpan::IndefiniteGridSpan(1),
@@ -177,13 +178,13 @@
       GridSpan::TranslatedDefiniteGridSpan(0, 2),
       GridSpan::TranslatedDefiniteGridSpan(2, 4)};
 
-  EXPECT_EQ(masonry_items.Size(), expected_spans.size());
+  EXPECT_EQ(grid_lanes_items.Size(), expected_spans.size());
 
-  const auto grid_axis_direction = node.Style().MasonryTrackSizingDirection();
-  for (wtf_size_t i = 0; auto& masonry_item : masonry_items) {
-    masonry_item.MaybeTranslateSpan(/*start_offset=*/0,
-                                    GridTrackSizingDirection::kForColumns);
-    EXPECT_EQ(masonry_item.resolved_position.Span(grid_axis_direction),
+  const auto grid_axis_direction = node.Style().GridLanesTrackSizingDirection();
+  for (wtf_size_t i = 0; auto& grid_lanes_item : grid_lanes_items) {
+    grid_lanes_item.MaybeTranslateSpan(/*start_offset=*/0,
+                                       GridTrackSizingDirection::kForColumns);
+    EXPECT_EQ(grid_lanes_item.resolved_position.Span(grid_axis_direction),
               expected_spans[i++]);
   }
 }
@@ -277,14 +278,14 @@
     </div>
   )HTML");
 
-  MasonryNode node(GetLayoutBoxByElementId("grid-lanes"));
+  GridLanesNode node(GetLayoutBoxByElementId("grid-lanes"));
 
   wtf_size_t max_end_line, start_offset;
   const GridLineResolver line_resolver(node.Style(), /*auto_repetitions=*/0);
-  const auto masonry_items = node.ConstructMasonryItems(line_resolver);
+  const auto grid_lanes_items = node.ConstructGridLanesItems(line_resolver);
   wtf_size_t unplaced_item_span_count = 0;
   const auto item_groups =
-      node.CollectItemGroups(line_resolver, masonry_items, max_end_line,
+      node.CollectItemGroups(line_resolver, grid_lanes_items, max_end_line,
                              start_offset, unplaced_item_span_count);
 
   EXPECT_EQ(item_groups.size(), 4u);
@@ -516,7 +517,7 @@
     #grid-lanes {
       height: 100px;
       display: grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-template-rows: 20px 1fr 30%;
     }
     </style>
@@ -886,7 +887,7 @@
     <style>
     #grid-lanes {
       display: grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-template-rows: repeat(auto-fit, 100px);
       height: 1000px;
     }
@@ -928,7 +929,7 @@
     <style>
     #grid-lanes {
       display: grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-template-rows: repeat(auto-fit, 100px);
       height: 1000px;
     }
@@ -970,7 +971,7 @@
   <style>
   #grid-lanes {
       display: grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-template-rows: repeat(5, 100px) repeat(auto-fit, 100px);
       height: 1000px;
   }
@@ -1014,7 +1015,7 @@
   <style>
   #grid-lanes {
       display: grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-template-rows: repeat(auto-fit, 100px) repeat(5, 100px);
       height: 1000px;
   }
@@ -1059,7 +1060,7 @@
     <style>
     #grid-lanes {
         display: grid-lanes;
-        masonry-direction: row;
+        grid-lanes-direction: row;
         grid-template-rows: repeat(auto-fit, auto);
         height: 1000px;
     }
@@ -1105,7 +1106,7 @@
     <style>
     #grid-lanes {
         display: grid-lanes;
-        masonry-direction: row;
+        grid-lanes-direction: row;
         grid-template-rows: repeat(auto-fit, auto);
         height: 1000px;
     }
@@ -1149,7 +1150,7 @@
     <style>
     #grid-lanes {
         display: grid-lanes;
-        masonry-direction: row;
+        grid-lanes-direction: row;
         grid-template-rows: repeat(5, 100px) repeat(auto-fit, auto);
         height: 1000px;
     }
@@ -1195,7 +1196,7 @@
   <style>
   #grid-lanes {
       display: grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-template-rows: repeat(auto-fit, auto) repeat(5, 100px);
       height: 1000px;
   }
diff --git a/third_party/blink/renderer/core/layout/masonry/masonry_running_positions.h b/third_party/blink/renderer/core/layout/masonry/masonry_running_positions.h
index 805657f9..66e0db2 100644
--- a/third_party/blink/renderer/core/layout/masonry/masonry_running_positions.h
+++ b/third_party/blink/renderer/core/layout/masonry/masonry_running_positions.h
@@ -32,12 +32,12 @@
                           const Vector<wtf_size_t>& collapsed_track_indexes)
       : running_positions_(/*size=*/track_collection.EndLineOfImplicitGrid(),
                            LayoutUnit()),
-        auto_placement_cursor_(style.IsReverseMasonryDirection()
+        auto_placement_cursor_(style.IsReverseGridLanesDirection()
                                    ? track_collection.EndLineOfImplicitGrid()
                                    : 0),
         tie_threshold_(tie_threshold),
         is_dense_packing_(style.IsGridAutoFlowAlgorithmDense()),
-        is_reverse_direction_(style.IsReverseMasonryDirection()) {
+        is_reverse_direction_(style.IsReverseGridLanesDirection()) {
     // To avoid placing items in collapsed tracks, set such tracks to the max
     // size.
     for (wtf_size_t index : collapsed_track_indexes) {
diff --git a/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
index c18d5ce..0e2103c4 100644
--- a/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
@@ -919,8 +919,8 @@
           style, borders, size, item);
     } else {
       rect = MasonryLayoutAlgorithm::ComputeOutOfFlowItemContainingRect(
-          To<LayoutMasonry>(containing_box).CachedPlacementData(), layout_data,
-          style, borders, size, item);
+          To<LayoutGridLanes>(containing_box).CachedPlacementData(),
+          layout_data, style, borders, size, item);
     }
 
     return {.writing_direction = style.GetWritingDirection(),
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index 265817f..74d379e 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -1161,26 +1161,26 @@
     return ComputedGridTemplate(SpecifiedGridTemplateRows());
   }
 
-  // Masonry utility functions.
-  GridTrackSizingDirection MasonryTrackSizingDirection() const {
-    switch (MasonryDirection()) {
-      case EMasonryDirection::kColumn:
-      case EMasonryDirection::kColumnReverse:
+  // Grid Lanes utility functions.
+  GridTrackSizingDirection GridLanesTrackSizingDirection() const {
+    switch (GridLanesDirection()) {
+      case EGridLanesDirection::kColumn:
+      case EGridLanesDirection::kColumnReverse:
         return kForColumns;
-      case EMasonryDirection::kRow:
-      case EMasonryDirection::kRowReverse:
+      case EGridLanesDirection::kRow:
+      case EGridLanesDirection::kRowReverse:
         return kForRows;
     }
     NOTREACHED();
   }
 
-  bool IsReverseMasonryDirection() const {
-    const auto masonry_direction = MasonryDirection();
-    return (masonry_direction == EMasonryDirection::kColumnReverse ||
-            masonry_direction == EMasonryDirection::kRowReverse);
+  bool IsReverseGridLanesDirection() const {
+    const auto grid_lanes_direction = GridLanesDirection();
+    return (grid_lanes_direction == EGridLanesDirection::kColumnReverse ||
+            grid_lanes_direction == EGridLanesDirection::kRowReverse);
   }
 
-  // Grid axis utility functions, usable in Grid and Masonry.
+  // Grid axis utility functions, usable in Grid and Grid Lanes.
   const GridTrackList& AutoTracks(
       GridTrackSizingDirection track_direction) const {
     return (track_direction == kForColumns) ? GridAutoColumns()
@@ -2686,14 +2686,14 @@
            display == EDisplay::kTableCaption;
   }
 
-  static GridTrackSizingDirection MasonryTrackSizingDirection(
-      EMasonryDirection direction) {
+  static GridTrackSizingDirection GridLanesTrackSizingDirection(
+      EGridLanesDirection direction) {
     switch (direction) {
-      case EMasonryDirection::kColumn:
-      case EMasonryDirection::kColumnReverse:
+      case EGridLanesDirection::kColumn:
+      case EGridLanesDirection::kColumnReverse:
         return kForColumns;
-      case EMasonryDirection::kRow:
-      case EMasonryDirection::kRowReverse:
+      case EGridLanesDirection::kRow:
+      case EGridLanesDirection::kRowReverse:
         return kForRows;
     }
     NOTREACHED();
diff --git a/third_party/blink/renderer/core/style/computed_style_constants.h b/third_party/blink/renderer/core/style/computed_style_constants.h
index efa4b03e..ec0a6142 100644
--- a/third_party/blink/renderer/core/style/computed_style_constants.h
+++ b/third_party/blink/renderer/core/style/computed_style_constants.h
@@ -96,6 +96,10 @@
   kPseudoIdViewTransitionImagePair,
   kPseudoIdViewTransitionOld,
   kPseudoIdViewTransitionNew,
+
+  kPseudoIdOverscrollAreaParent,
+  kPseudoIdOverscrollClientArea,
+
   // Internal IDs follow:
   kPseudoIdFirstLineInherited,
 
@@ -117,15 +121,12 @@
   kPseudoIdPickerSelect,
   kPseudoIdPermissionIcon,
 
-  kPseudoIdOverscrollAreaParent,
-  kPseudoIdOverscrollClientArea,
-
   // Special values follow:
   kAfterLastInternalPseudoId,
   kPseudoIdInvalid,
   kFirstPublicPseudoId = kPseudoIdFirstLine,
   kLastTrackedPublicPseudoId = kPseudoIdGrammarError,
-  kLastPublicPseudoId = kPseudoIdViewTransitionNew,
+  kLastPublicPseudoId = kPseudoIdOverscrollClientArea,
   kFirstInternalPseudoId = kPseudoIdFirstLineInherited,
 };
 
diff --git a/third_party/blink/renderer/core/testing/fuzztest_utils/README.md b/third_party/blink/renderer/core/testing/fuzztest_utils/README.md
index bd29f38..5d67e07 100644
--- a/third_party/blink/renderer/core/testing/fuzztest_utils/README.md
+++ b/third_party/blink/renderer/core/testing/fuzztest_utils/README.md
@@ -38,6 +38,9 @@
     to empty string)
   - `GetPredefinedNodes()` (optional) - provides a fixed initial DOM structure
     instead of generating random nodes. Modifications are still fuzzed.
+  - `UseShadowDOM()` (optional) - returns true to enable shadow DOM fuzzing,
+    which wraps nodes in shadow hosts with optional slot projection (defaults
+    to false)
 - **`AnyDomScenarioForSpec()`** - Generates FuzzTest domains from specifications
 - **`DomScenarioRunner`** - Base class that executes `DomScenario` test cases
   by creating initial DOM, applying modifications, and calling
diff --git a/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario.cc b/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario.cc
index cc3f434..e420b9a 100644
--- a/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario.cc
+++ b/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario.cc
@@ -41,6 +41,9 @@
   if (!scenario.stylesheet.empty()) {
     base::StrAppend(&out, {"Stylesheet: ", scenario.stylesheet, "\n"});
   }
+  if (scenario.use_shadow_dom) {
+    base::StrAppend(&out, {"Shadow DOM: enabled\n"});
+  }
 
   base::StrAppend(
       &out, {"Nodes (", base::ToString(scenario.node_specs.size()), "):\n"});
@@ -111,6 +114,30 @@
       base::StrAppend(&out,
                       {"   Attributes:", initial_attrs_str, " (Unchanged)\n"});
     }
+
+    if (scenario.use_shadow_dom) {
+      if (initial.in_shadow_dom != modified.in_shadow_dom) {
+        base::StrAppend(
+            &out,
+            {"   In Shadow DOM: ", (initial.in_shadow_dom ? "true" : "false"),
+             " -> ", (modified.in_shadow_dom ? "true" : "false"), "\n"});
+      } else {
+        base::StrAppend(&out, {"   In Shadow DOM: ",
+                               (initial.in_shadow_dom ? "true" : "false"),
+                               " (Unchanged)\n"});
+      }
+
+      if (initial.use_slot_projection != modified.use_slot_projection) {
+        base::StrAppend(
+            &out, {"   Use Slot Projection: ",
+                   (initial.use_slot_projection ? "true" : "false"), " -> ",
+                   (modified.use_slot_projection ? "true" : "false"), "\n"});
+      } else {
+        base::StrAppend(&out, {"   Use Slot Projection: ",
+                               (initial.use_slot_projection ? "true" : "false"),
+                               " (Unchanged)\n"});
+      }
+    }
   }
   base::StrAppend(&out, {"--------------------------------------"});
   return out;
@@ -121,12 +148,19 @@
 // Domain for a node's state (parent index, attributes, styles, text).
 fuzztest::Domain<NodeState> AnyNodeState(DomScenarioDomainSpecification* spec,
                                          int num_nodes) {
+  fuzztest::Domain<bool> in_shadow_dom_domain =
+      spec->UseShadowDOM() ? fuzztest::ElementOf({true, false})
+                           : fuzztest::Just(false);
+  fuzztest::Domain<bool> use_slot_projection_domain =
+      spec->UseShadowDOM() ? fuzztest::ElementOf({true, false})
+                           : fuzztest::Just(false);
   return fuzztest::StructOf<NodeState>(
       fuzztest::InRange(kIndexOfRootElement, num_nodes - 1),
       fuzztest::OptionalOf(fuzztest::VectorOf(spec->AnyAttributeNameValuePair())
                                .WithMaxSize(spec->GetMaxAttributesPerNode())),
       fuzztest::OptionalOf(spec->AnyStyles()),
-      fuzztest::OptionalOf(spec->AnyText()));
+      fuzztest::OptionalOf(spec->AnyText()), in_shadow_dom_domain,
+      use_slot_projection_domain);
 }
 
 // Domain for a complete node specification (tag + initial state + modified
@@ -179,9 +213,9 @@
               .WithSize(num_nodes);
         }();
 
-        return fuzztest::StructOf<DomScenario>(spec->GetRootElementTag(),
-                                               node_specs_domain,
-                                               spec->AnyStylesheet());
+        return fuzztest::StructOf<DomScenario>(
+            spec->GetRootElementTag(), node_specs_domain, spec->AnyStylesheet(),
+            fuzztest::Just(spec->UseShadowDOM()));
       },
       fuzztest::InRange(1, spec->GetMaxDomNodes()));
 }
diff --git a/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario.h b/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario.h
index ad9c4efa..7eddb862 100644
--- a/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario.h
+++ b/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario.h
@@ -29,6 +29,8 @@
   std::optional<std::vector<std::pair<QualifiedName, std::string>>> attributes;
   std::optional<std::string> styles;
   std::optional<std::string> text;
+  bool in_shadow_dom;
+  bool use_slot_projection;  // Only meaningful if in_shadow_dom is true.
 };
 
 // Specification for a single DOM node, including its tag, initial state,
@@ -46,6 +48,7 @@
   QualifiedName root_tag;
   std::vector<NodeSpecification> node_specs;
   std::string stylesheet;
+  bool use_shadow_dom;
   std::string ToString() const;
 };
 
@@ -75,6 +78,10 @@
   virtual std::optional<std::vector<NodeSpecification>> GetPredefinedNodes() {
     return std::nullopt;
   }
+
+  // If true, nodes can be fuzzed to have in_shadow_dom=true, which causes
+  // the runner to wrap them in a div shadow host.
+  virtual bool UseShadowDOM() { return false; }
 };
 
 // Domain building functions
diff --git a/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario_runner.cc b/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario_runner.cc
index 72aa198..7b26c05 100644
--- a/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario_runner.cc
+++ b/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario_runner.cc
@@ -8,10 +8,12 @@
 
 #include "base/command_line.h"
 #include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/types/optional_ref.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/dom/text.h"
 #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
 #include "third_party/blink/renderer/core/html/html_head_element.h"
@@ -55,6 +57,7 @@
 void DomScenarioRunner::RunTest(const DomScenario& input) {
   Element* root = nullptr;
   HeapVector<Member<Element>> created_elements;
+  shadow_host_counter_ = 0;
   LogIfEnabled(base::StrCat({"\n\n", input.ToString()}));
   CreateInitialDOM(input, root, created_elements);
   ApplyModifications(root, input.node_specs, created_elements);
@@ -102,8 +105,9 @@
   for (size_t i = 0; i < input.node_specs.size(); ++i) {
     const auto& node_spec = input.node_specs[i];
     Element* element = created_elements[i];
-    SetParent(element, node_spec.initial_state.parent_index, root,
-              created_elements);
+    SetParent(element, i, node_spec.initial_state.parent_index, root,
+              created_elements, node_spec.initial_state.in_shadow_dom,
+              node_spec.initial_state.use_slot_projection);
   }
 
   document.UpdateStyleAndLayoutTree();
@@ -123,7 +127,8 @@
     const auto& node_spec = node_specs[i];
     const auto& modified_state = node_spec.modified_state;
     Element* element = created_elements[i];
-    SetParent(element, modified_state.parent_index, root, created_elements);
+    SetParent(element, i, modified_state.parent_index, root, created_elements,
+              modified_state.in_shadow_dom, modified_state.use_slot_projection);
     // Set attributes first because there's a chance that one of the fuzzed
     // attributes is style. Should that occur we want the style domain to win.
     SetElementAttributes(element, modified_state.attributes);
@@ -213,12 +218,21 @@
 
 void DomScenarioRunner::SetParent(
     Element* child,
+    size_t child_index,
     int parent_index,
     Element* root,
-    const HeapVector<Member<Element>>& created_elements) {
+    const HeapVector<Member<Element>>& created_elements,
+    bool in_shadow_dom,
+    bool use_slot_projection) {
   DCHECK(child);
   DCHECK(root);
 
+  // If shadow DOM usage is indicated, wrap the element and update child to
+  // point to the shadow host.
+  if (in_shadow_dom) {
+    child = WrapInShadowDOM(child, use_slot_projection, child_index);
+  }
+
   // TODO(crbug.com/445771451): Remove this temporary workaround for slot
   // assignment recursion bug. Skip moving slot elements that would trigger
   // recursive slot assignment recalculation during appendChild operations.
@@ -280,6 +294,21 @@
     }
     base::StrAppend(&result, {">"});
 
+    // Shadow root content if present.
+    bool has_shadow_root = element->GetShadowRoot() != nullptr;
+    if (has_shadow_root) {
+      ShadowRoot* shadow_root = element->GetShadowRoot();
+      base::StrAppend(&result,
+                      {"\n", indent_str, "  #shadow-root (",
+                       (shadow_root->IsOpen() ? "open" : "closed"), ")"});
+      for (Node* child = shadow_root->firstChild(); child;
+           child = child->nextSibling()) {
+        base::StrAppend(&result, {"\n"});
+        SerializeNode(child, result, indent + 2);
+      }
+      base::StrAppend(&result, {"\n", indent_str, "  #end-shadow-root"});
+    }
+
     // Children.
     bool has_children = false;
     bool is_style_element = element->tagName() == "STYLE";
@@ -303,6 +332,8 @@
     // Closing tag.
     if (has_children) {
       base::StrAppend(&result, {indent_str});
+    } else if (has_shadow_root) {
+      base::StrAppend(&result, {"\n", indent_str});
     }
     base::StrAppend(&result, {"</", element->tagName().Utf8(), ">"});
   } else if (Text* text = DynamicTo<Text>(node)) {
@@ -330,4 +361,50 @@
   return result;
 }
 
+Element* DomScenarioRunner::WrapInShadowDOM(Element* element,
+                                            bool use_slot_projection,
+                                            size_t child_index) {
+  Document& document = GetDocument();
+  Element* shadow_host = document.CreateRawElement(
+      html_names::TagToQualifiedName(html_names::HTMLTag::kDiv));
+  // Use a counter to ensure unique IDs across initial and modification phases.
+  shadow_host->setAttribute(
+      html_names::kIdAttr,
+      AtomicString(StrCat({"shadow-host-", String::Number(child_index), "-",
+                           String::Number(shadow_host_counter_++)})));
+  ShadowRoot& shadow_root =
+      shadow_host->AttachShadowRootForTesting(ShadowRootMode::kOpen);
+
+  // Projection: element appended to shadow_host, projected through <slot>.
+  // Otherwise: element directly in shadow root.
+  if (use_slot_projection) {
+    Element* slot = document.CreateRawElement(
+        html_names::TagToQualifiedName(html_names::HTMLTag::kSlot));
+    AtomicString slot_name =
+        AtomicString(StrCat({"slot-", String::Number(child_index)}));
+    slot->setAttribute(html_names::kNameAttr, slot_name);
+    shadow_root.appendChild(slot);
+    element->setAttribute(html_names::kSlotAttr, slot_name);
+  }
+
+  // Try to append element to the appropriate parent.
+  ContainerNode* target = use_slot_projection
+                              ? static_cast<ContainerNode*>(shadow_host)
+                              : static_cast<ContainerNode*>(&shadow_root);
+  DummyExceptionStateForTesting exception_state;
+  target->appendChild(element, exception_state);
+
+  // If `appendChild` failed, return the unwrapped element so `SetParent` can
+  // add it directly to the DOM.
+  if (exception_state.HadException() || element->parentNode() == nullptr) {
+    LogIfEnabled(base::StrCat({"appendChild failed for element ",
+                               base::NumberToString(child_index), ": ",
+                               exception_state.Message().Utf8(),
+                               ", returning unwrapped element"}));
+    return element;
+  }
+
+  return shadow_host;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario_runner.h b/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario_runner.h
index a6ba6b7..93e7755e 100644
--- a/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario_runner.h
+++ b/third_party/blink/renderer/core/testing/fuzztest_utils/dom_scenario_runner.h
@@ -76,9 +76,12 @@
       base::optional_ref<
           const std::vector<std::pair<QualifiedName, std::string>>> attributes);
   void SetParent(Element* child,
+                 size_t child_index,
                  int parent_index,
                  Element* root,
-                 const HeapVector<Member<Element>>& created_elements);
+                 const HeapVector<Member<Element>>& created_elements,
+                 bool in_shadow_dom,
+                 bool use_slot_projection);
 
   // Get DOM tree as string with pretty-printing.
   std::string GetDOMTreeAsString();
@@ -88,8 +91,17 @@
   void SerializeNode(Node* node, std::string& result, int indent);
   std::string EscapeString(const std::string& str);
 
+  // Creates a shadow host wrapper for an element and returns the shadow host.
+  // Called by `SetParent` when shadow DOM usage is indicated.
+  Element* WrapInShadowDOM(Element* element,
+                           bool use_slot_projection,
+                           size_t child_index);
+
   // Set from --enable-dom-fuzzer-logging at construction time.
   bool logging_enabled_ = false;
+
+  // Counter for generating unique shadow host IDs within a test case.
+  inline static int shadow_host_counter_ = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/accessibility/testing/accessibility_dom_fuzztest.cc b/third_party/blink/renderer/modules/accessibility/testing/accessibility_dom_fuzztest.cc
index 1a275b1..43bf6255c 100644
--- a/third_party/blink/renderer/modules/accessibility/testing/accessibility_dom_fuzztest.cc
+++ b/third_party/blink/renderer/modules/accessibility/testing/accessibility_dom_fuzztest.cc
@@ -66,6 +66,7 @@
                         "#id_4::before, #id_5::before { content: '['; } "
                         "#id_4::after, #id_5::after { content: ']'; }")));
   }
+  bool UseShadowDOM() override { return true; }
 };
 
 class CanvasFallbackContent : public HtmlAndAria {
diff --git a/third_party/blink/renderer/modules/mediarecorder/BUILD.gn b/third_party/blink/renderer/modules/mediarecorder/BUILD.gn
index 29885ef9..f1658bcf 100644
--- a/third_party/blink/renderer/modules/mediarecorder/BUILD.gn
+++ b/third_party/blink/renderer/modules/mediarecorder/BUILD.gn
@@ -5,7 +5,6 @@
 import("//build/buildflag_header.gni")
 import("//media/media_options.gni")
 import("//third_party/blink/renderer/modules/modules.gni")
-import("//third_party/libaom/options.gni")
 
 blink_modules_sources("mediarecorder") {
   sources = [
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 8e88e1e..0308337 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -3229,6 +3229,12 @@
       status: "stable",
     },
     {
+      name: "ManualText",
+      public: true,
+      status: "test",
+      base_feature: "none",
+    },
+    {
       name: "MaskDeserializationTimeForCrossOriginMessages",
       status: "stable",
     },
@@ -4479,7 +4485,7 @@
     {
       // https://github.com/w3c/csswg-drafts/issues/9367#issuecomment-1854280461
       name: "ScrollTimelineNamedRangeScroll",
-      status: "test",
+      status: "experimental",
     },
     // Implements documentElement.scrollTop/Left and bodyElement.scrollTop/Left
     // as per the spec, matching other Web engines.
@@ -4496,7 +4502,7 @@
     // https://chromestatus.com/feature/5195073796177920
     {
       name: "SearchTextHighlightPseudo",
-      status: "experimental",
+      status: "stable",
     },
     // SecurePaymentConfirmation has shipped on some platforms, but its
     // availability is controlled by the browser process (via the
@@ -6038,7 +6044,7 @@
     },
     {
       name: "WindowControlsOverlay",
-      status: { "Android": "test", "default": "stable" },
+      status: { "Android": "stable", "default": "stable" },
       base_feature: "AndroidWindowControlsOverlay",
     },
     // If enabled, window.default[Ss]tatus will be supported. This is disabled
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index e6407e3c1..409c67df 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -9934,3 +9934,6 @@
 crbug.com/42202693 virtual/shared_array_buffer_on_desktop/external/wpt/wasm/jsapi/memory/to-resizable-buffer-shared.any.worker.html [ Failure Pass ]
 crbug.com/42202693 virtual/shared_array_buffer_on_desktop/external/wpt/wasm/jsapi/memory/to-resizable-buffer.any.html [ Failure Pass ]
 crbug.com/42202693 virtual/shared_array_buffer_on_desktop/external/wpt/wasm/jsapi/memory/to-resizable-buffer.any.worker.html [ Failure Pass ]
+
+# Gardener 2025-11-25
+crbug.com/463661453 [ Mac ] virtual/threaded/wpt_internal/scheduler/integration_tests/rendering-starvation.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 9477ab9c..10e767f4 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -2168,8 +2168,7 @@
     "expires": "never",
     "owners": [
       "gastonr@microsoft.com",
-      "gerchiko@microsoft.com",
-      "yshalivskyy@microsoft.com"
+      "gerchiko@microsoft.com"
     ]
   },
   "This suite is incompatible with the default WPT Origin Isolation mode of",
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-alignment.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-alignment.html
index eae8e14..2be5080 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-alignment.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-alignment.html
@@ -22,7 +22,7 @@
 
     .grid-lanes {
         display: grid-lanes;
-        masonry-direction: row;
+        grid-lanes-direction: row;
         grid-template-rows: repeat(5, 150px);
         position: relative;
         padding: 20px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-intrinsic-sizing-oof.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-intrinsic-sizing-oof.html
index 8e724bd..8a37cafe 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-intrinsic-sizing-oof.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-intrinsic-sizing-oof.html
@@ -17,7 +17,7 @@
 
     .grid-lanes {
         display: grid-lanes;
-        masonry-direction: row;
+        grid-lanes-direction: row;
         grid-template-rows: repeat(3, auto);
         border: 2px solid blue;
         padding: 5px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-out-of-flow-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-out-of-flow-001.html
index f038e08..8f6199b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-out-of-flow-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-out-of-flow-001.html
@@ -16,7 +16,7 @@
 
     .grid-lanes {
         display: grid-lanes;
-        masonry-direction: row;
+        grid-lanes-direction: row;
         grid-template-rows: repeat(4, 8rem);
         align-content: center;
         position: relative;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-out-of-flow-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-out-of-flow-002.html
index cbcd5806..f7ef120 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-out-of-flow-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-out-of-flow-002.html
@@ -16,7 +16,7 @@
 
     .grid-lanes {
         display: grid-lanes;
-        masonry-direction: row;
+        grid-lanes-direction: row;
         grid-template-rows: repeat(4, 60px);
         border: 1px solid blue;
         padding: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-out-of-flow-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-out-of-flow-003.html
index bebeff17..296dfad 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-out-of-flow-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-out-of-flow-003.html
@@ -17,7 +17,7 @@
 
     .grid-lanes {
         display: grid-lanes;
-        masonry-direction: row;
+        grid-lanes-direction: row;
         grid-template-rows: 50px 100px 150px;
         border: 5px solid blue;
         margin: 30px 20px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-positioned-item-dynamic-change.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-positioned-item-dynamic-change.html
index cd01a6e..90bd36b3 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-positioned-item-dynamic-change.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/abspos/row-grid-lanes-positioned-item-dynamic-change.html
@@ -9,7 +9,7 @@
   <style>
     .grid-lanes {
         display: grid-lanes;
-        masonry-direction: row;
+        grid-lanes-direction: row;
         grid-template-rows: 75px 25px;
         position: relative;
         width: 100px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-align-content-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-align-content-003.html
index 8ce72c9..898a6b5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-align-content-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-align-content-003.html
@@ -21,7 +21,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: repeat(4,auto);
   background: content-box silver;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-align-content-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-align-content-004.html
index 30b21cf..2fbe537 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-align-content-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-align-content-004.html
@@ -20,7 +20,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: repeat(4,20px);
   background: content-box silver;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-justify-content-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-justify-content-003.html
index cb1b1d2..af68cb4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-justify-content-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-justify-content-003.html
@@ -20,7 +20,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: repeat(4,20px);
   background: content-box silver;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-justify-content-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-justify-content-004.html
index 3dca90a..b70a42c8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-justify-content-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/grid-lanes-justify-content-004.html
@@ -20,7 +20,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: repeat(4,20px);
   background: content-box silver;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-align-items-center-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-align-items-center-001.html
index c8448ef3..785e80aa6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-align-items-center-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-align-items-center-001.html
@@ -8,7 +8,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     item-tolerance: 0;
     grid-template-rows: repeat(3, 100px);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-align-items-end-align-self-start-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-align-items-end-align-self-start-001.html
index 9691473..e2c3a4d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-align-items-end-align-self-start-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-align-items-end-align-self-start-001.html
@@ -7,7 +7,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     item-tolerance: 0;
     grid-template-rows: repeat(3, 100px);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-001.html
index a28dc00..ed03656 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-001.html
@@ -15,7 +15,7 @@
       display: grid-lanes;
       gap: 2px;
       grid-template-rows: repeat(3, 40px);
-      masonry-direction: row;
+      grid-lanes-direction: row;
       color: #444;
       border: 1px solid;
       padding: 2px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-002.html
index b87cd97..240a39c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-002.html
@@ -15,7 +15,7 @@
       display: grid-lanes;
       gap: 2px;
       grid-template-rows: repeat(3, 40px);
-      masonry-direction: row;
+      grid-lanes-direction: row;
       color: #444;
       border: 1px solid;
       padding: 2px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-003.html
index 00f88a3b..5934bd640 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-003.html
@@ -12,7 +12,7 @@
       width: 100px;
       height: 250px;
       grid-template-rows: repeat(4, 1fr);
-      masonry-direction: row;
+      grid-lanes-direction: row;
       gap: 5px;
       border: 1px solid black;
       margin-bottom: 20px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-001.html
index 61628b2..74977cc0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-001.html
@@ -15,7 +15,7 @@
     .grid-lanes {
       position: relative;
       display: grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-template-rows: 100px 150px;
       width: 200px;
       height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-002.html
index 867ccced9..6397770 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-002.html
@@ -15,7 +15,7 @@
     .grid-lanes {
       position: relative;
       display: grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-template-rows: 80px 80px 80px;
       width: 150px;
       height: 290px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-003.html
index af8be5d2..0b23d860 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-003.html
@@ -15,7 +15,7 @@
     .grid-lanes {
       position: relative;
       display: grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-template-rows: 90px 90px 90px;
       width: 150px;
       height: 320px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-004.html
index 9f22c813..2cd3265d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-alignment-positioned-items-004.html
@@ -15,7 +15,7 @@
     .grid-lanes {
       position: relative;
       display: grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-template-rows: 80px 80px 80px;
       width: 200px;
       height: 290px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-overflow-alignment-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-overflow-alignment-001.html
index 87fd71b..48cdc940 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-overflow-alignment-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/alignment/row-overflow-alignment-001.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     item-tolerance: 0;
     grid-template-rows: 70px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-content-baseline-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-content-baseline-001.html
index 9a248b7..c1917f5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-content-baseline-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-content-baseline-001.html
@@ -25,7 +25,7 @@
 }
 .c {
   grid: repeat(4, auto) / none;
-  masonry-direction: row;
+  grid-lanes-direction: row;
 }
 
 span {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-001.html
index a17c4856..ffae683 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-001.html
@@ -26,7 +26,7 @@
 }
 .c {
   grid: repeat(4, auto) / none;
-  masonry-direction: row;
+  grid-lanes-direction: row;
 }
 
 span {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-002a.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-002a.html
index 4883af84..da0793f1 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-002a.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-002a.html
@@ -28,7 +28,7 @@
 }
 .c {
   grid: repeat(4, auto) / none;
-  masonry-direction: row;
+  grid-lanes-direction: row;
 }
 .ae { align-content: end; }
 .je { justify-content: end; }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-002b.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-002b.html
index 3fbfbaf..926649d0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-002b.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-002b.html
@@ -28,7 +28,7 @@
 }
 .c {
   grid: repeat(4, auto) / none;
-  masonry-direction: row;
+  grid-lanes-direction: row;
 }
 .ae { align-content: end; }
 .je { justify-content: end; }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/fragmentation/grid-lanes-fragmentation-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/fragmentation/grid-lanes-fragmentation-003.html
index 3f687de..2a956a8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/fragmentation/grid-lanes-fragmentation-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/fragmentation/grid-lanes-fragmentation-003.html
@@ -29,7 +29,7 @@
 
 .grid {
   display: grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid: 20px auto 30px / none;
   border: solid;
   border-width: 3px 1px 7px 5px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/gap/row-gaps-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/gap/row-gaps-001.html
index 4e67479..3b231ce8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/gap/row-gaps-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/gap/row-gaps-001.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     item-tolerance: 0;
     grid-template-rows: repeat(3, 100px);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-lanes-columns-item-containing-block-is-grid-content-width.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-lanes-columns-item-containing-block-is-grid-content-width.html
index 4c1b8a1..a32ce6fd3 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-lanes-columns-item-containing-block-is-grid-content-width.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-lanes-columns-item-containing-block-is-grid-content-width.html
@@ -9,7 +9,7 @@
 grid {
     display: grid-lanes;
     grid-template-rows: auto;
-    masonry-direction: row;
+    grid-lanes-direction: row;
 }
 </style>
 </head>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/column-explicit-placement-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/column-explicit-placement-003.html
index a3ec6b70..a47bb9dd 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/column-explicit-placement-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/column-explicit-placement-003.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: column;
+    grid-lanes-direction: column;
     background: gray;
     item-tolerance: 0;
     grid-template-columns: auto auto auto;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-001.html
index 80dcc93..80c7701f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-001.html
@@ -7,7 +7,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     item-tolerance: 0;
     height: 200px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-002.html
index 37dd341..3ec59593 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-002.html
@@ -7,7 +7,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     item-tolerance: 0;
     height: 200px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-003.html
index efe9c01..b493f06 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-003.html
@@ -7,7 +7,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     item-tolerance: 0;
     height: 200px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-004.html
index a251abdb..f5e0dd07 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-004.html
@@ -7,7 +7,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     item-tolerance: 0;
     height: 200px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-005.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-005.html
index 80c92ce..9279fad 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-005.html
@@ -7,7 +7,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     item-tolerance: 0;
     height: 200px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-006.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-006.html
index f8e991688..df5e0026 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-006.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     item-tolerance: 0;
     height: 200px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-007.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-007.html
index a9bf4a3..9fef0986 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-007.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-007.html
@@ -7,7 +7,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     item-tolerance: 0;
     height: 200px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-008.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-008.html
index 9f394bb0..6eb977b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-008.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-008.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(3, 100px);
     background: gray;
     item-tolerance: 0;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-auto.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-auto.html
index 12b222a7..6c30895 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-auto.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-auto.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: repeat(4,auto);
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-fr.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-fr.html
index 3b90fa9..4ecc445 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-fr.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-fr.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: 1fr 2fr 1fr 1fr;
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-mix1.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-mix1.html
index 4b16021..dca37332 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-mix1.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-mix1.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: 1fr 2fr min-content max-content;
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-mix2.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-mix2.html
index 5503bc3..2e639d4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-mix2.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-001-mix2.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   /* keep fixed values small enough for spanners to have an effect */
   grid-template-rows: 1.1ch auto 1.4ch 1fr;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-auto.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-auto.html
index c855577f..b518683 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-auto.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-auto.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: repeat(4,auto);
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-fr.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-fr.html
index 1a03bc1..597c1c2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-fr.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-fr.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: 1fr 2fr 1fr 1fr;
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-mix1.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-mix1.html
index 30c9623..b234ff40 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-mix1.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-mix1.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: 1fr 2fr min-content max-content;
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-mix2.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-mix2.html
index f637f4eb..5ec92a37 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-mix2.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-002-mix2.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   /* keep fixed values small enough for spanners to have an effect */
   grid-template-rows: 1.1ch auto 1.4ch 1fr;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-auto.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-auto.html
index 105b7b3..81599b5b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-auto.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-auto.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: repeat(4,auto);
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-fr.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-fr.html
index 54466a2..210f800 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-fr.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-fr.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: 1fr 2fr 1fr 1fr;
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix1.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix1.html
index ac4bb2f..a6f8ad62 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix1.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix1.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: 1fr 2fr min-content max-content;
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix2.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix2.html
index 5a5f279..627438c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix2.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix2.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   /* keep fixed values small enough for spanners to have an effect */
   grid-template-rows: 1.1ch auto 1.4ch 1fr;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-auto.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-auto.html
index 88155ae..88ae4d3 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-auto.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-auto.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: repeat(4,auto);
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-fr.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-fr.html
index d44e661a..7665908 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-fr.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-fr.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: 1fr 2fr 1fr 1fr;
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix1.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix1.html
index 9014fd0..b4ec2fc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix1.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix1.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: 1fr 2fr min-content max-content;
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix2.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix2.html
index f446a520..2fffd89a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix2.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix2.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   /* keep fixed values small enough for spanners to have an effect */
   grid-template-rows: 1.1ch auto 1.4ch 1fr;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-005.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-005.html
index c21bfbd..8a596bf 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-005.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: repeat(4,auto);
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-006.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-006.html
index 8f66eb9..669fb82 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-006.html
@@ -15,7 +15,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1px 2px;
   grid-template-rows: repeat(4,auto);
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-007.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-007.html
index 25349ce..694d4a47 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-007.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-007.html
@@ -15,7 +15,7 @@
 
 grid {
   display: grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   gap: 1rem 1rem;
   grid-template-rows: repeat(4,100px);
   border: 1px solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/row-defined-height.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/row-defined-height.html
index e30ccc3..587e22f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/row-defined-height.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/row-defined-height.html
@@ -9,7 +9,7 @@
     background: gray;
     position: relative;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: auto auto auto;
     width: 300px;
     height: 100px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/row-intrinsic-inline-container-size.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/row-intrinsic-inline-container-size.html
index ebcb9ff..5dffedd 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/row-intrinsic-inline-container-size.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/row-intrinsic-inline-container-size.html
@@ -6,7 +6,7 @@
 <link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
 <style>
 .grid-lanes {
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(3, 20px);
     display: grid-lanes;
     background: gray;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-001.html
index 9b46864..fc74806 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-001.html
@@ -9,7 +9,7 @@
     item-tolerance: 0;
     grid-template-columns: repeat(3, 50px);
     gap: 10px;
-    masonry-direction: column-reverse;
+    grid-lanes-direction: column-reverse;
 }
 </style>
 <body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-002-ref.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-002-ref.html
index 02f19bf..c58c9b8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-002-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-002-ref.html
@@ -6,7 +6,7 @@
     item-tolerance: 0;
     grid-template-columns: repeat(3, auto);
     gap: 10px;
-    masonry-direction: column;
+    grid-lanes-direction: column;
 }
 </style>
 <body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-002.html
index 41424d4..ee49316 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-002.html
@@ -9,7 +9,7 @@
     item-tolerance: 0;
     grid-template-columns: repeat(3, auto);
     gap: 10px;
-    masonry-direction: column-reverse;
+    grid-lanes-direction: column-reverse;
 }
 </style>
 <body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-003-ref.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-003-ref.html
index c7f2b5c..07797ba 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-003-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-003-ref.html
@@ -10,7 +10,7 @@
 }
 </style>
 <body>
-  <p>The CSS property `direction` is correctly applied on top of `masonry-direction`</p>
+  <p>The CSS property `direction` is correctly applied on top of `grid-lanes-direction`</p>
   <div class="grid-lanes">
     <div style="background: lightgreen; width: min-content;">The cat still cannot be separated from milk</div>
     <div style="background: lightskyblue;">The cat cannot be separated from milk</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-003.html
index ed6a734..26a7652 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-003.html
@@ -9,12 +9,12 @@
     item-tolerance: 0;
     grid-template-columns: repeat(3, min-content);
     gap: 10px;
-    masonry-direction: column-reverse;
+    grid-lanes-direction: column-reverse;
     direction: rtl;
 }
 </style>
 <body>
-  <p>The CSS property `direction` is correctly applied on top of `masonry-direction`</p>
+  <p>The CSS property `direction` is correctly applied on top of `grid-lanes-direction`</p>
   <div class="grid-lanes">
     <div style="background: lavender;">Some larger words in this sentence</div>
     <div style="background: lightskyblue;">The cat cannot be separated from milk</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-001.html
index fd563b2..6a9c85d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-001.html
@@ -8,7 +8,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(3, 50px);
     width: 170px;
     grid-auto-flow: dense;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-002.html
index 9f54348..54327df 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-002.html
@@ -8,7 +8,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(3, 50px);
     padding: 10px;
     grid-auto-flow: dense;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-003.html
index 52f2bfb..9ca1d32 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-003.html
@@ -8,7 +8,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(3, 50px);
     grid-auto-flow: dense;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-004.html
index df7fa2bed..9c116b5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-004.html
@@ -7,7 +7,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(4, 50px);
     grid-auto-flow: dense;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-001.html
index d59df294..d1c2304 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-001.html
@@ -7,7 +7,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(4, 50px);
     grid-auto-flow: dense;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-002.html
index 8d65844..48c135d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-002.html
@@ -7,7 +7,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: 10px 10px 20px 15px 5px;
     grid-auto-flow: dense;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-003.html
index 6bf5f11..46b9fcb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-003.html
@@ -7,7 +7,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(3, 50px);
     padding: 10px;
     grid-auto-flow: dense;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-004.html
index 2288a1a..eb99098 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-004.html
@@ -7,7 +7,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(4, 50px);
     grid-auto-flow: dense;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-005.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-005.html
index de25d6c..5b2d6da 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/dense-packing/row-dense-packing-multi-span-005.html
@@ -9,7 +9,7 @@
     item-tolerance: 0;
     gap: 10px;
     padding: 10px;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(4, 60px);
     grid-auto-flow: dense;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/column-item-tolerance-infinite.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/column-item-tolerance-infinite.html
index 179f2c7b..05fc32d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/column-item-tolerance-infinite.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/column-item-tolerance-infinite.html
@@ -9,7 +9,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: column;
+    grid-lanes-direction: column;
     background: gray;
     grid-template-columns: repeat(4, 1fr);
     item-tolerance: infinite;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-001.html
index 7ac8f14..7da8c415 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-001.html
@@ -12,7 +12,7 @@
 
     grid {
       display: inline-grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-auto-flow: column;
       gap: 10px;
       grid-template-rows: repeat(2, 100px);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-002.html
index d3242fa1..3d54b38 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-002.html
@@ -12,7 +12,7 @@
 
     grid {
       display: inline-grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-auto-flow: column;
       gap: 10px;
       grid-template-rows: repeat(3, 100px);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-003.html
index be43cd1f..3468b0e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-003.html
@@ -12,7 +12,7 @@
 
     grid {
       display: inline-grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-auto-flow: column;
       gap: 10px;
       grid-template-rows: repeat(3, 100px);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-004.html
index 7cec820..9102688e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/item-tolerance-row-004.html
@@ -12,7 +12,7 @@
 
     grid {
       display: inline-grid-lanes;
-      masonry-direction: row;
+      grid-lanes-direction: row;
       grid-auto-flow: column;
       gap: 10px;
       grid-template-rows: repeat(2, 100px);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/row-initial-item-tolerance.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/row-initial-item-tolerance.html
index 0b04dc17..ae9fe78 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/row-initial-item-tolerance.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/row-initial-item-tolerance.html
@@ -9,7 +9,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     font: 50px/1 Ahem;
     grid-template-rows: repeat(2, 1fr);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/row-item-tolerance-infinite.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/row-item-tolerance-infinite.html
index 8c1ffd4..bc6659b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/row-item-tolerance-infinite.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/item-tolerance/row-item-tolerance-infinite.html
@@ -9,7 +9,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     grid-template-rows: repeat(4, 1fr);
     item-tolerance: infinite;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-001.html
index 31bac51..a6a441a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-001.html
@@ -9,7 +9,7 @@
     background: gray;
     position: relative;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: auto auto auto;
     width: 300px;
     padding: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-002.html
index 9ccf6e8..b6d5d61 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-002.html
@@ -10,7 +10,7 @@
     background: gray;
     item-tolerance: 0;
     grid-template-rows: auto auto auto;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     width: 870px;
     padding: 20px;
     gap: 20px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-max-content.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-max-content.html
index 66c4aa17..0eb8b0c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-max-content.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-max-content.html
@@ -13,7 +13,7 @@
     background: gray;
     position: relative;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: auto auto auto;
     width: max-content;
     padding: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-min-content.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-min-content.html
index 55e10c5..92e145b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-min-content.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-auto-placement-min-content.html
@@ -13,7 +13,7 @@
     background: gray;
     position: relative;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: auto auto auto;
     width: min-content;
     padding: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-negative-margin-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-negative-margin-001.html
index 444318e..bbad2c0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-negative-margin-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-negative-margin-001.html
@@ -7,7 +7,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
 }
 </style>
 <body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-001.html
index af54573..ae998aa4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-001.html
@@ -9,7 +9,7 @@
     item-tolerance: 0;
     grid-template-rows: repeat(3, 50px);
     gap: 10px;
-    masonry-direction: row-reverse;
+    grid-lanes-direction: row-reverse;
 }
 </style>
 <body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-002-ref.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-002-ref.html
index ec4fac4..54af7b41 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-002-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-002-ref.html
@@ -6,7 +6,7 @@
     item-tolerance: 0;
     grid-template-rows: repeat(3, auto);
     gap: 10px;
-    masonry-direction: row;
+    grid-lanes-direction: row;
 }
 </style>
 <body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-002.html
index e26b659..9bdbea6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-002.html
@@ -9,7 +9,7 @@
     item-tolerance: 0;
     grid-template-rows: repeat(3, auto);
     gap: 10px;
-    masonry-direction: row-reverse;
+    grid-lanes-direction: row-reverse;
 }
 </style>
 <body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-003-ref.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-003-ref.html
index a988d82..177b3977 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-003-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-003-ref.html
@@ -5,13 +5,13 @@
     display: grid-lanes;
     item-tolerance: 0;
     grid-template-rows: repeat(3, 50px);
-    masonry-direction: row;
+    grid-lanes-direction: row;
     direction: rtl;
     gap: 10px;
 }
 </style>
 <body>
-  <p>The CSS property `direction` is correctly applied on top of `masonry-direction`</p>
+  <p>The CSS property `direction` is correctly applied on top of `grid-lanes-direction`</p>
   <div class="grid-lanes">
     <div style="background: lightskyblue;">The cat cannot be separated from milk</div>
     <div style="background: lightgreen; width: min-content;">milk</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-003.html
index cf7f104..0cec5ed5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-003.html
@@ -9,12 +9,12 @@
     item-tolerance: 0;
     grid-template-rows: repeat(3, 50px);
     gap: 10px;
-    masonry-direction: row-reverse;
+    grid-lanes-direction: row-reverse;
     direction: rtl;
 }
 </style>
 <body>
-  <p>The CSS property `direction` is correctly applied on top of `masonry-direction`</p>
+  <p>The CSS property `direction` is correctly applied on top of `grid-lanes-direction`</p>
   <div class="grid-lanes">
     <div style="background: lavender;">Some larger words in this sentence</div>
     <div style="background: lightgreen; width: min-content;">milk</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-automatic-minimum-for-auto.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-automatic-minimum-for-auto.html
index 4e7a9e6..bec9a605 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-automatic-minimum-for-auto.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-automatic-minimum-for-auto.html
@@ -8,7 +8,7 @@
 <link rel="stylesheet" href="/fonts/ahem.css">
 
 <style>
-.grid-lanes { display: grid-lanes; masonry-direction: row; font: 10px/1 Ahem; }
+.grid-lanes { display: grid-lanes; grid-lanes-direction: row; font: 10px/1 Ahem; }
 
 .minHeight10 { min-height: 10px; }
 .minHeight20 { min-height: 20px; }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-flex-and-intrinsic-sizes.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-flex-and-intrinsic-sizes.html
index 5bbd4c3e..ff8811be 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-flex-and-intrinsic-sizes.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-flex-and-intrinsic-sizes.html
@@ -6,7 +6,7 @@
 <meta name="assert" content="An item spanning >1 track, at least one of which is flexible, must have a zero automatic minimum size (and thus, not grow to accommodate its children).">
 
 <style>
-.grid-lanes { display: grid-lanes; masonry-direction: row; position: relative; grid-template-rows: repeat(12, 1fr); height: 100px; width: 100px; }
+.grid-lanes { display: grid-lanes; grid-lanes-direction: row; position: relative; grid-template-rows: repeat(12, 1fr); height: 100px; width: 100px; }
 .test { grid-row: 1 / span 8; background: red; }
 .over { grid-row: 1 / span 8; height: 100%; background: green; position: absolute; }
 .under { grid-row: 9 / span 4; background: green; }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-flex-spanning-items.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-flex-spanning-items.html
index f84e911..84852896 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-flex-spanning-items.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-flex-spanning-items.html
@@ -7,7 +7,7 @@
 <style>
 #masonry {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: 1fr 30px;
     border: 10px solid fuchsia;
     height: min-content;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-flex-track-intrinsic-sizes.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-flex-track-intrinsic-sizes.html
index 78ec153..94c6e54 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-flex-track-intrinsic-sizes.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-flex-track-intrinsic-sizes.html
@@ -9,7 +9,7 @@
 <style>
 #masonry {
   display: grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   width: 60px;
   height: 60px;
   border: solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-intrinsic-track-sizes.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-intrinsic-track-sizes.html
index b8b452d..c645a992 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-intrinsic-track-sizes.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-intrinsic-track-sizes.html
@@ -8,7 +8,7 @@
 <style>
 #masonry {
   display: grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   width: 120px;
   height: 120px;
   border: solid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-minmax-img-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-minmax-img-001.html
index 243f341..b49f285e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-minmax-img-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-minmax-img-001.html
@@ -8,7 +8,7 @@
 <style>
 #masonry {
     display: inline-grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: minmax(auto, 0);
     height: 200px;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-minmax-img-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-minmax-img-002.html
index 0dd368c..27bf4dbb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-minmax-img-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-minmax-img-002.html
@@ -8,7 +8,7 @@
 <style>
 .grid-lanes {
     display: inline-grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: minmax(auto, 0);
     border: 5px solid goldenrod;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-percentage-sizes-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-percentage-sizes-001.html
index c98c22a..7b51c4d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-percentage-sizes-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-percentage-sizes-001.html
@@ -12,7 +12,7 @@
 
 .grid-lanes {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   height: 10px;
   width: 10px;
   grid-template-rows: 3px auto 4px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-percentage-sizes-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-percentage-sizes-002.html
index 29fb4fa0..0f3b902a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-percentage-sizes-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-percentage-sizes-002.html
@@ -12,7 +12,7 @@
 
 .grid-lanes {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   height: 10px;
   width: 10px;
   grid-template-rows: 3px auto 4px 2px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-percentage-sizes-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-percentage-sizes-003.html
index 7b1916f..df6232e2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-percentage-sizes-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-item-percentage-sizes-003.html
@@ -12,7 +12,7 @@
 
 .grid-lanes {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   height: 10px;
   width: 10px;
   grid-template-rows: 3px auto 4px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-contribution-baseline-shim.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-contribution-baseline-shim.html
index 714d011..c0e62eca 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-contribution-baseline-shim.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-contribution-baseline-shim.html
@@ -7,7 +7,7 @@
 <style>
 .grid-lanes {
   display: grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   position: relative;
   font-size: 0;
   height: 0;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-contribution-with-percentages.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-contribution-with-percentages.html
index 547e11a..d84d4461 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-contribution-with-percentages.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-contribution-with-percentages.html
@@ -7,7 +7,7 @@
 <style>
 #masonry {
   display: grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   height: 50px;
   width: 50px;
   grid-template-rows: auto;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-size-grid-items-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-size-grid-items-001.html
index 97bbad1..a5519cb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-size-grid-items-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-size-grid-items-001.html
@@ -16,7 +16,7 @@
 
 #constrained-masonry {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     width: 10px;
     height: 10px;
     grid-template-rows: minmax(auto, 0px);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-size-grid-items-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-size-grid-items-002.html
index 5719fa18..d42aaf1e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-size-grid-items-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-size-grid-items-002.html
@@ -9,7 +9,7 @@
 <style>
 .grid-lanes {
   display: grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   border: solid thick;
   font: 10px/1 Ahem;
   width: 50px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-size-grid-items-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-size-grid-items-003.html
index 81f8aac7..9837bca4f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-size-grid-items-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/items/row-minimum-size-grid-items-003.html
@@ -9,7 +9,7 @@
 <style>
 .grid-lanes {
   display: grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   border: solid thick;
   font: 10px/1 Ahem;
   width: 50px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-001.html
index 34704884..de939a8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-001.html
@@ -8,7 +8,7 @@
 #masonry {
   display: grid-lanes;
   font: 50px/1 Ahem;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: auto auto;
   justify-content: start;
   align-content: start;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-002.html
index ac9f815..ca375601 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-002.html
@@ -8,7 +8,7 @@
 #grid {
   display: grid-lanes;
   font: 50px/1 Ahem;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: auto auto;
   justify-content: start;
   align-content: start;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-003.html
index 75272e64..3e42e87b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-003.html
@@ -8,7 +8,7 @@
 #grid {
   display: grid-lanes;
   font: 50px/1 Ahem;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: auto auto;
   justify-content: start;
   align-content: start;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-004.html
index fd6fbff1d..28b72732 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-004.html
@@ -7,7 +7,7 @@
 <style>
 #grid {
   display: grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   font: 50px/1 Ahem;
   grid-template-rows: auto auto;
   justify-content: start;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-005.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-005.html
index 79012a99..e760178 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/order/row-order-property-auto-placement-005.html
@@ -8,7 +8,7 @@
 #grid {
   display: grid-lanes;
   font: 50px/1 Ahem;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: auto auto;
   justify-content: start;
   align-content: start;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-direction-computed.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-direction-computed.html
index d9447654..e55e3630 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-direction-computed.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-direction-computed.html
@@ -2,7 +2,7 @@
 <html>
 <head>
   <meta charset="utf-8">
-  <title>CSS Masonry: masonry-direction getComputedStyle()</title>
+  <title>CSS Grid Lanes: grid-lanes-direction getComputedStyle()</title>
   <link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
   <link rel="help" href="https://drafts.csswg.org/css-grid-3">
   <script src="/resources/testharness.js"></script>
@@ -14,10 +14,10 @@
   <div id="target"></div>
   </div>
   <script>
-    test_computed_value("masonry-direction", "row");
-    test_computed_value("masonry-direction", "column");
-    test_computed_value("masonry-direction", "row-reverse");
-    test_computed_value("masonry-direction", "column-reverse");
+    test_computed_value("grid-lanes-direction", "row");
+    test_computed_value("grid-lanes-direction", "column");
+    test_computed_value("grid-lanes-direction", "row-reverse");
+    test_computed_value("grid-lanes-direction", "column-reverse");
   </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-direction-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-direction-invalid.html
index dddcb2f24..e8e5621 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-direction-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-direction-invalid.html
@@ -2,10 +2,10 @@
 <html>
 <head>
   <meta charset="utf-8">
-  <title>CSS Masonry: parsing masonry-direction with invalid values</title>
+  <title>CSS Grid Lanes: parsing grid-lanes-direction with invalid values</title>
   <link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
   <link rel="help" href="https://drafts.csswg.org/css-grid-3">
-  <meta name="assert" content="masonry-direction supports only the grammar 'row | row-reverse | column | column-reverse'.">
+  <meta name="assert" content="grid-lanes-direction supports only the grammar 'row | row-reverse | column | column-reverse'.">
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
   <script src="/css/support/parsing-testcommon.js"></script>
@@ -14,8 +14,8 @@
   <div id="target"></div>
   </div>
   <script>
-    test_invalid_value("masonry-direction", "auto");
-    test_invalid_value("masonry-direction", "column row-reverse");
+    test_invalid_value("grid-lanes-direction", "auto");
+    test_invalid_value("grid-lanes-direction", "column row-reverse");
   </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-direction-valid.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-direction-valid.html
index a901304..440ecc7 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-direction-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-direction-valid.html
@@ -2,10 +2,10 @@
 <html>
 <head>
   <meta charset="utf-8">
-  <title>CSS Masonry: parsing masonry-direction with valid values</title>
+  <title>CSS Grid Lanes: parsing grid-lanes-direction with valid values</title>
   <link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
   <link rel="help" href="https://drafts.csswg.org/css-grid-3">
-  <meta name="assert" content="masonry-direction supports the full grammar 'row | row-reverse | column | column-reverse'.">
+  <meta name="assert" content="grid-lanes-direction supports the full grammar 'row | row-reverse | column | column-reverse'.">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <script src="/css/support/parsing-testcommon.js"></script>
@@ -13,10 +13,10 @@
 <body>
   <div id="target"></div>
   <script>
-    test_valid_value("masonry-direction", "row");
-    test_valid_value("masonry-direction", "row-reverse");
-    test_valid_value("masonry-direction", "column");
-    test_valid_value("masonry-direction", "column-reverse");
+    test_valid_value("grid-lanes-direction", "row");
+    test_valid_value("grid-lanes-direction", "row-reverse");
+    test_valid_value("grid-lanes-direction", "column");
+    test_valid_value("grid-lanes-direction", "column-reverse");
   </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-flow-valid.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-flow-valid.html
index 6334d9f..5d6b4a8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-flow-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-flow-valid.html
@@ -15,44 +15,44 @@
     test_valid_value("grid-lanes-flow", "column normal");
     test_valid_value("grid-lanes-flow", "column reverse");
     test_shorthand_value('grid-lanes-flow', 'column normal', {
-      'masonry-direction': 'column',
+      'grid-lanes-direction': 'column',
       'grid-lanes-fill': 'normal'
     });
     test_shorthand_value('grid-lanes-flow', 'column reverse', {
-      'masonry-direction': 'column',
+      'grid-lanes-direction': 'column',
       'grid-lanes-fill': 'reverse'
     });
 
     test_valid_value("grid-lanes-flow", "row normal");
     test_valid_value("grid-lanes-flow", "row reverse");
     test_shorthand_value('grid-lanes-flow', 'row normal', {
-      'masonry-direction': 'row',
+      'grid-lanes-direction': 'row',
       'grid-lanes-fill': 'normal'
     });
     test_shorthand_value('grid-lanes-flow', 'row reverse', {
-      'masonry-direction': 'row',
+      'grid-lanes-direction': 'row',
       'grid-lanes-fill': 'reverse'
     });
 
     test_valid_value("grid-lanes-flow", "column-reverse normal");
     test_valid_value("grid-lanes-flow", "column-reverse reverse");
     test_shorthand_value('grid-lanes-flow', 'column-reverse normal', {
-      'masonry-direction': 'column-reverse',
+      'grid-lanes-direction': 'column-reverse',
       'grid-lanes-fill': 'normal'
     });
     test_shorthand_value('grid-lanes-flow', 'column-reverse reverse', {
-      'masonry-direction': 'column-reverse',
+      'grid-lanes-direction': 'column-reverse',
       'grid-lanes-fill': 'reverse'
     });
 
     test_valid_value("grid-lanes-flow", "row-reverse normal");
     test_valid_value("grid-lanes-flow", "row-reverse reverse");
     test_shorthand_value('grid-lanes-flow', 'row-reverse normal', {
-      'masonry-direction': 'row-reverse',
+      'grid-lanes-direction': 'row-reverse',
       'grid-lanes-fill': 'normal'
     });
     test_shorthand_value('grid-lanes-flow', 'row-reverse reverse', {
-      'masonry-direction': 'row-reverse',
+      'grid-lanes-direction': 'row-reverse',
       'grid-lanes-fill': 'reverse'
     });
   </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-shorthand-serialization.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-shorthand-serialization.html
index 5cde968..9357d7cd 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-shorthand-serialization.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-shorthand-serialization.html
@@ -9,14 +9,14 @@
 </head>
 <body>
   <script>
-    function testValidGridLanes(gridTemplateRowsValue, gridTemplateColumnsValue, gridTemplateAreasValue, masonryDirectionValue, gridLanesFillValue, serializedGridLanesValue) {
+    function testValidGridLanes(gridTemplateRowsValue, gridTemplateColumnsValue, gridTemplateAreasValue, gridLanesDirectionValue, gridLanesFillValue, serializedGridLanesValue) {
     test(()=>{
       const root = document.documentElement;
     const properties = [
       ["gridTemplateRows", gridTemplateRowsValue],
       ["gridTemplateColumns", gridTemplateColumnsValue],
       ["gridTemplateAreas", gridTemplateAreasValue],
-      ["masonryDirection", masonryDirectionValue],
+      ["gridLanesDirection", gridLanesDirectionValue],
       ["gridLanesFill", gridLanesFillValue],
     ];
     for (const [property, value] of properties) {
@@ -27,7 +27,7 @@
   }, `grid-template-rows: ${gridTemplateRowsValue},
       grid-template-columns: ${gridTemplateColumnsValue},
       grid-template-areas: ${gridTemplateAreasValue},
-      masonry-direction: ${masonryDirectionValue},
+      grid-lanes-direction: ${gridLanesDirectionValue},
       grid-lanes-fill: ${gridLanesFillValue} should be valid.`);
 }
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-shorthand-valid.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-shorthand-valid.html
index 33a83e7e..05a09f67 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-shorthand-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/parsing/grid-lanes-shorthand-valid.html
@@ -48,31 +48,31 @@
     test_shorthand_value('grid-lanes', 'none', {
       'grid-template-columns': 'none',
       'grid-template-areas': 'none',
-      'masonry-direction': 'column',
+      'grid-lanes-direction': 'column',
       'grid-lanes-fill': 'normal'
     });
     test_shorthand_value('grid-lanes', '10px reverse', {
       'grid-template-columns': '10px',
       'grid-template-areas': 'none',
-      'masonry-direction': 'column',
+      'grid-lanes-direction': 'column',
       'grid-lanes-fill': 'reverse'
     });
     test_shorthand_value('grid-lanes', '"b a" 20% 40% column normal', {
       'grid-template-columns': '20% 40%',
       'grid-template-areas': '"b a"',
-      'masonry-direction': 'column',
+      'grid-lanes-direction': 'column',
       'grid-lanes-fill': 'normal'
     });
     test_shorthand_value('grid-lanes', '"b b a" 1fr 2fr 3fr row', {
       'grid-template-rows': '1fr 2fr 3fr',
       'grid-template-areas': '"b" "b" "a"',
-      'masonry-direction': 'row',
+      'grid-lanes-direction': 'row',
       'grid-lanes-fill': 'normal'
     });
     test_shorthand_value('grid-lanes', 'repeat(2, auto) row-reverse', {
       'grid-template-rows': 'repeat(2, auto)',
       'grid-template-areas': 'none',
-      'masonry-direction': 'row-reverse',
+      'grid-lanes-direction': 'row-reverse',
       'grid-lanes-fill': 'normal'
     });
   </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/row-empty-grid-lanes-container-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/row-empty-grid-lanes-container-001.html
index 3f74262..b93aac0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/row-empty-grid-lanes-container-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/row-empty-grid-lanes-container-001.html
@@ -6,7 +6,7 @@
 <style>
 .empty-container {
   display: grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
 };
 </style>
 <body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/row-empty-grid-lanes-container-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/row-empty-grid-lanes-container-002.html
index 808567e6..9fbf0fc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/row-empty-grid-lanes-container-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/row-empty-grid-lanes-container-002.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: gray;
     position: relative;
     item-tolerance: 0;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/row-min-max-content-container.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/row-min-max-content-container.html
index 88dea7c7..e75d799 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/row-min-max-content-container.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/row-min-max-content-container.html
@@ -13,7 +13,7 @@
     background: gray;
     position: relative;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: auto auto auto;
     padding: 10px;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001a.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001a.html
index ef2c7f7d..7245dc4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001a.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001a.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 50px 80px 40px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001b.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001b.html
index 103bedf..2a49b75e4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001b.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001b.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 50px 80px 40px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001c.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001c.html
index a66960516..5e21e98 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001c.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001c.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 50px 80px 40px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001d.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001d.html
index c63a0a7..28edf2e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001d.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001d.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 50px 80px 40px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001e.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001e.html
index 0176c60..74a9eaea 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001e.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001e.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 50px 80px 40px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002a.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002a.html
index 201d3ed..16bf3625 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002a.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002a.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002b.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002b.html
index 1257582..a61a411 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002b.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002b.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002c.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002c.html
index 8908589..8ca7969 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002c.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002c.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002d.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002d.html
index 6df7c6da..db23ae35 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002d.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002d.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002e.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002e.html
index 6ca490e..bf9db78 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002e.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002e.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002f.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002f.html
index 4c8b836df..c91bf62 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002f.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002f.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002g.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002g.html
index 72c9512..622511e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002g.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002g.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002h.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002h.html
index ad6626d..2cb565c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002h.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002h.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002i.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002i.html
index d91eecb..8bfab05 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002i.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002i.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001a.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001a.html
index 2ef6f92..afb5738 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001a.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001a.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 50px 80px 40px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001b.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001b.html
index 5844c72..a647a50 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001b.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001b.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 50px 80px 40px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001c.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001c.html
index ffd933aa..c6bbfc6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001c.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001c.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 50px 80px 40px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001d.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001d.html
index 5c3768a9..99f993a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001d.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001d.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 50px 80px 40px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002a.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002a.html
index f197367..d9848d88 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002a.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002a.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002b.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002b.html
index ae271256..a7a61b2f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002b.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002b.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002c.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002c.html
index 086ee27..c44ca31 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002c.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002c.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002d.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002d.html
index 012b9c86..3c6c209 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002d.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002d.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002e.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002e.html
index b6bb8c8..68145f7 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002e.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002e.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002f.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002f.html
index 194460173..c045ce1 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002f.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002f.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002g.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002g.html
index b56e4320..51f1f90 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002g.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002g.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002h.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002h.html
index 6f64a4d..d6a985d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002h.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002h.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002i.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002i.html
index b28e19e0..7709112a5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002i.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002i.html
@@ -16,7 +16,7 @@
 
 grid {
   display: inline-grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 40px 30px 20px;
   gap: 4px 2px;
   padding: 1px 3px 5px 7px;
@@ -25,7 +25,7 @@
   background: lightgrey content-box;
 }
 .rows {
-  masonry-direction: column;
+  grid-lanes-direction: column;
   grid-template-columns: 40px 30px 20px;
 }
 item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-001.html
index f3fce65..4dcd963 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-001.html
@@ -9,7 +9,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto);
     width: 300px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-002.html
index d843b6e..f2e92b7 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-002.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto);
     width: 200px;
     height: 360px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-003.html
index 6c7a57a..1f7a613 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-003.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto);
     width: 200px;
     height: 360px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-004.html
index 5b1ef9a..377dc15 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-004.html
@@ -9,7 +9,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: auto auto auto auto;
     grid-gap: 20px;
     width: 260px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-005.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-005.html
index f7a90e1e..608f3bbf 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-005.html
@@ -9,7 +9,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     /*
       This is not currently a valid track definition and will fall back to
       none.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-006.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-006.html
index c1fc0767..649ec7c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-006.html
@@ -8,7 +8,7 @@
     display: inline-grid-lanes;
     background: green;
     aspect-ratio: 1/1;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto);
     min-height: 60px;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-007.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-007.html
index 673f460c6..d9f49e9e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-007.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-007.html
@@ -8,7 +8,7 @@
     display: inline-grid-lanes;
     background: green;
     aspect-ratio: 1/1;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto);
     min-height: 60%;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-008.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-008.html
index 8019bf99..85d8d6b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-008.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-008.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: inline-grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: green;
     width: 100px;
     min-height: 60%;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-009.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-009.html
index b50da25..636bc1b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-009.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-009.html
@@ -7,7 +7,7 @@
 .grid-lanes {
     position: relative;
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto);
     min-width: 300px;
     min-height: 200px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-010.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-010.html
index 66e49d6..8e9460a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-010.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-010.html
@@ -8,7 +8,7 @@
     position: relative;
     display: grid-lanes;
     grid-template-rows: repeat(auto-fill, auto);
-    masonry-direction: row;
+    grid-lanes-direction: row;
     max-width: 100px;
     min-width: 250px;
     max-height: 50px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-011.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-011.html
index 571298b..332cd1f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-011.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-011.html
@@ -8,7 +8,7 @@
   display: grid-lanes;
   border: solid thick;
   margin: 10px;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: repeat(auto-fill, auto auto);
   grid-row-gap: 100px;
   height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-012.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-012.html
index baf27c5..c9974c4e6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-012.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-012.html
@@ -9,7 +9,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, auto);
     height: 300px;
     width: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-013.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-013.html
index b534d89..dec36c9 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-013.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-013.html
@@ -9,7 +9,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, auto);
     width: 300px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-014.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-014.html
index eb6bff12..6ddec3f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-014.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-014.html
@@ -9,7 +9,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, auto);
     width: 200px;
     height: 500px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-015.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-015.html
index 3460295..f178404 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-015.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-015.html
@@ -9,7 +9,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, auto);
     width: 200px;
     height: 1000px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-016.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-016.html
index d3c247c..529cb9ea 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-016.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-016.html
@@ -9,7 +9,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, auto);
     width: 200px;
     height: 1000px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-017-ref.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-017-ref.html
index ee5e7e9..0094f91 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-017-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-017-ref.html
@@ -3,7 +3,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(9, auto);
     width: 200px;
     height: 500px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-017.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-017.html
index abb02a5..ac06e87 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-017.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-017.html
@@ -8,7 +8,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, auto);
     width: 200px;
     height: 500px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-018.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-018.html
index 73d3453c..2f6d8ef 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-018.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-018.html
@@ -8,7 +8,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: 50px repeat(5, 50px) repeat(auto-fit, auto);
     width: 200px;
     height: 500px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-019.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-019.html
index 81eb47e..566eff9 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-019.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-019.html
@@ -9,7 +9,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: 100px repeat(auto-fit, auto);
     width: 500px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-020.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-020.html
index 082002e..34d2a76 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-020.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-020.html
@@ -9,7 +9,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(5, 100px) repeat(auto-fit, auto);
     width: 200px;
     height: 1000px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-021.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-021.html
index 518727e..510532f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-021.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-021.html
@@ -9,7 +9,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, auto) repeat(5, 100px);
     width: 200px;
     height: 1000px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-022.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-022.html
index ca5ea418..3a403d149 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-022.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-022.html
@@ -8,7 +8,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(4, 50px) repeat(auto-fit, auto) repeat(4, 50px);
     width: 200px;
     height: 500px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-023.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-023.html
index 635f8f1..688d3cc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-023.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-023.html
@@ -8,7 +8,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto);
     height: 400px;
     gap: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-024.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-024.html
index 017a8f5..f19dfe0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-024.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-024.html
@@ -8,7 +8,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto);
     height: 400px;
     gap: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-025.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-025.html
index 1362fd98..ccf81378 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-025.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-025.html
@@ -8,7 +8,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto);
     height: 400px;
     gap: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-026.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-026.html
index f3fdf43..071aaae 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-026.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-026.html
@@ -8,7 +8,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto);
     height: 400px;
     gap: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-027.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-027.html
index 3dadbe6..881d3c5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-027.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-027.html
@@ -8,7 +8,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto);
     height: 400px;
     gap: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-028.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-028.html
index 9dc81477..4a7577b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-028.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-028.html
@@ -11,7 +11,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto);
     width: auto;
     height: auto;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-no-items-crash.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-no-items-crash.html
index cb31cc0..eddee77 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-no-items-crash.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-auto-no-items-crash.html
@@ -6,7 +6,7 @@
 <style>
 #masonry {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, minmax(20px, 30px) auto);
 }
 </style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-001.html
index 35b7c74..e528404 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-001.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, fit-content(100px));
     width: 300px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-002.html
index c762fea..285f8f8b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-002.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, fit-content(100px));
     width: 300px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-003.html
index adf3607..dd21840 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-003.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     /*
       This is not currently a valid track definition and will fall back to
       none.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-004.html
index 961a43ff..66b30eb8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-004.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, fit-content(100px) fit-content(100px));
     width: 300px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-005.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-005.html
index 2b733ba5..a83e295 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-fit-content-005.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, fit-content(100px));
     height: 300px;
     width: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-001.html
index 225a72a3..c988ad7 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-001.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, max-content);
     width: 300px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-002.html
index 2829fd3..25a247e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-002.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, max-content);
     width: 300px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-003.html
index f3338d0c..b02114c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-003.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     /*
       This is not currently a valid track definition and will fall back to
       none.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-004.html
index 66e6ffcb..0506fb1e3 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-004.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, max-content max-content);
     width: 300px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-005.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-005.html
index b6fc355..8067460 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-max-content-005.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, max-content);
     height: 300px;
     width: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-001.html
index 340c678..699fed5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-001.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, min-content);
     width: 300px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-002.html
index 2ecea3fc..65c40c1 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-002.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, min-content);
     width: 300px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-003.html
index 88e017c..658d72d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-003.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     /*
       This is not currently a valid track definition and will fall back to
       none.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-004.html
index 381a4bd..746561f7 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-004.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, min-content min-content);
     width: 300px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-005.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-005.html
index aeee13a..29f524e0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-min-content-005.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, min-content);
     height: 300px;
     width: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-mixed-intrinsic-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-mixed-intrinsic-001.html
index 06baea4b..897962f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-mixed-intrinsic-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-mixed-intrinsic-001.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, min-content max-content fit-content(100px) auto);
     height: 500px;
     width: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-mixed-intrinsic-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-mixed-intrinsic-002.html
index 599b504..6474d55f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-mixed-intrinsic-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/intrinsic-auto-repeat/row-auto-repeat-mixed-intrinsic-002.html
@@ -10,7 +10,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, auto fit-content(100px));
     height: 300px;
     width: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-001.html
index b6463dbd..1bdbd9a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-001.html
@@ -9,7 +9,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, 100px);
     width: 300px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-002.html
index c6a4e31..66604741 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-002.html
@@ -9,7 +9,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, 25%);
     width: 200px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-003.html
index 8c84d17..7df35fa 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-003.html
@@ -8,7 +8,7 @@
     display: inline-grid-lanes;
     background: green;
     aspect-ratio: 1/1;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, 50px);
     min-height: 60px;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-004.html
index 4fbf1f7c..94dbf9d4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-004.html
@@ -8,7 +8,7 @@
     display: inline-grid-lanes;
     background: green;
     aspect-ratio: 1/1;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, 50px);
     min-height: 60%;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-005.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-005.html
index 7744713e..20d28c7 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-005.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     height: 100px;
     grid-template-rows: repeat(auto-fill, minmax(50px, 25px));
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-006.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-006.html
index 8cd9be9..91a73d2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-006.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: inline-grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     background: green;
     width: 100px;
     min-height: 60%;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-007.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-007.html
index 6e13bd2..0440a1190 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-007.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-007.html
@@ -7,7 +7,7 @@
 .grid-lanes {
     position: relative;
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, 50px);
     min-width: 300px;
     min-height: 200px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-008.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-008.html
index 3b1673d..122022af 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-008.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-008.html
@@ -7,7 +7,7 @@
 .grid-lanes {
     position: relative;
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, 50px);
     min-width: 50%;
     min-height: 80%;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-009.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-009.html
index 7bbcecb..5e3c166 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-009.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-009.html
@@ -9,7 +9,7 @@
 .grid-lanes {
     position: relative;
     display: inline-grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, 20%);
     min-height: 50%;
     width: 100px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-010.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-010.html
index 8312697..9f96f127 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-010.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-010.html
@@ -7,7 +7,7 @@
 .grid-lanes {
     position: relative;
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, 50px);
     max-width: 50%;
     max-height: 80%;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-011.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-011.html
index d9adabb..95573f48 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-011.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-011.html
@@ -8,7 +8,7 @@
     position: relative;
     display: grid-lanes;
     grid-template-rows: repeat(auto-fill, 50px);
-    masonry-direction: row;
+    grid-lanes-direction: row;
     max-width: 100px;
     min-width: 250px;
     max-height: 50px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-012.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-012.html
index f794281..85e6f93 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-012.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-012.html
@@ -8,7 +8,7 @@
   display: grid-lanes;
   border: solid thick;
   margin: 10px;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: repeat(auto-fill, 50px 50px);
   grid-row-gap: 100px;
   height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-013.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-013.html
index d5eb826..383df25 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-013.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-013.html
@@ -7,7 +7,7 @@
 .grid-lanes {
   display: grid-lanes;
   border: solid thick;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-template-rows: 10px 20px repeat(auto-fill, 30px 40px) 50px 60px;
   height: 300px;
   background: pink;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-014.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-014.html
index 2bbd080..70342493 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-014.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-014.html
@@ -8,7 +8,7 @@
   display: inline-grid-lanes;
   border: 1px solid black;
   grid-template-rows: [u] repeat(auto-fill, [v] 10px [w] 10px [x] 10px [y]) [z];
-  masonry-direction: row;
+  grid-lanes-direction: row;
   grid-row-gap: 3px;
   width: min-content;
   /* Does not fit a whole-number of repetitions */
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-015.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-015.html
index dd72b3a..f3a6892 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-015.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-015.html
@@ -9,7 +9,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, 100px);
     height: 300px;
     width: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-016.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-016.html
index d58a65a..139e406c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-016.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-016.html
@@ -9,7 +9,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, 100px);
     width: 200px;
     height: 500px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-017.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-017.html
index 78db7760..7c156d96 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-017.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-017.html
@@ -9,7 +9,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, 100px);
     width: 200px;
     height: 1000px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-018.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-018.html
index 631d6a5..6200469 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-018.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-018.html
@@ -9,7 +9,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, 100px);
     width: 200px;
     height: 1000px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-019.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-019.html
index 995bb56..4f884e98 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-019.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-019.html
@@ -8,7 +8,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, 50px);
     width: 200px;
     height: 500px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-020.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-020.html
index 5979304a..210dd868 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-020.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-020.html
@@ -8,7 +8,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: 50px repeat(5, 50px) repeat(auto-fit, 50px);
     width: 200px;
     height: 500px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-021.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-021.html
index cca36eb..fc9ca7a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-021.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-021.html
@@ -9,7 +9,7 @@
     display: grid-lanes;
     background: gray;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: 100px repeat(auto-fit, 100px);
     width: 500px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-022.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-022.html
index 622a4a8..96921ce 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-022.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-022.html
@@ -9,7 +9,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(5, 100px) repeat(auto-fit, 100px);
     width: 200px;
     height: 1000px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-023.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-023.html
index 49d4826..44832ab 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-023.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-023.html
@@ -9,7 +9,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fit, 100px) repeat(5, 100px);
     width: 200px;
     height: 1000px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-024.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-024.html
index 92543e8a..9fe68528 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-024.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-024.html
@@ -8,7 +8,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(4, 50px) repeat(auto-fit, 50px) repeat(4, 50px);
     width: 200px;
     height: 500px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-025.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-025.html
index cbfbe62..040dd7a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-025.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-025.html
@@ -8,7 +8,7 @@
 .grid-lanes {
     display: grid-lanes;
     item-tolerance: 0;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: repeat(auto-fill, 100px);
     height: 400px;
     width: 200px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/dynamic-grid-track-direction.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/dynamic-grid-track-direction.html
index 6b2ca5f..041ecd6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/dynamic-grid-track-direction.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/dynamic-grid-track-direction.html
@@ -20,7 +20,7 @@
     assert_equals(computedStyle.getPropertyValue('grid-template-columns'),
       "100px 100px");
 
-    container.style.masonryDirection = 'row';
+    container.style.gridLanesDirection = 'row';
     const computedStyleAfter = window.getComputedStyle(container);
     assert_equals(computedStyleAfter.getPropertyValue('grid-template-rows'),
       "100px 100px 100px");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-001.html
index d8eabd13..983337d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-001.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     item-tolerance: 0;
     grid-template-rows: 5% repeat(3, 10px 15%) repeat(1, 15px 5px 20px);
     height: 500px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-002.html
index 0452c44..0f96ee5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-002.html
@@ -7,7 +7,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: minmax(15px, min-content) max-content auto;
     background-color: gray;
     height: 100px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-003.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-003.html
index a76e67f..1df3410 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-003.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: 1fr 5fr 3fr 1fr;
     width: 100px;
     height: 100px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-004.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-004.html
index 854f462..b856f7e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-004.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     grid-template-rows: 20px 1fr 30%;
     width: 100px;
     height: 100px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-005.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-005.html
index c8e1f8ee..e1944c4a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/row-track-sizing-005.html
@@ -6,7 +6,7 @@
 <style>
 .grid-lanes {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     item-tolerance: 0;
     grid-template-rows: auto auto auto;
     gap: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-template-rows-intrinsic-auto-repeat-computed-implicit-track.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-template-rows-intrinsic-auto-repeat-computed-implicit-track.tentative.html
index 0933988..3c98aae 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-template-rows-intrinsic-auto-repeat-computed-implicit-track.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-template-rows-intrinsic-auto-repeat-computed-implicit-track.tentative.html
@@ -7,7 +7,7 @@
 <style>
 #target {
   display: grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   height: 1px;
   font-size: 1px;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-template-rows-intrinsic-auto-repeat-computed-withcontent.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-template-rows-intrinsic-auto-repeat-computed-withcontent.tentative.html
index 67b8e0d5a..a3130bed 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-template-rows-intrinsic-auto-repeat-computed-withcontent.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-template-rows-intrinsic-auto-repeat-computed-withcontent.tentative.html
@@ -12,7 +12,7 @@
 <style>
   #target {
     display: grid-lanes;
-    masonry-direction: row;
+    grid-lanes-direction: row;
     font-size: 40px;
     min-height: 200px;
     height: 300px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-template-rows-intrinsic-auto-repeat-computed.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-template-rows-intrinsic-auto-repeat-computed.tentative.html
index 20216f5..4d7e31a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-template-rows-intrinsic-auto-repeat-computed.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-template-rows-intrinsic-auto-repeat-computed.tentative.html
@@ -7,7 +7,7 @@
 <style>
 #target {
   display: grid-lanes;
-  masonry-direction: row;
+  grid-lanes-direction: row;
   height: 1px;
   font-size: 1px;
 }
diff --git a/third_party/blink/web_tests/external/wpt/ua-client-hints/useragentdata.https.any.js b/third_party/blink/web_tests/external/wpt/ua-client-hints/useragentdata.https.any.js
index d94cd71..0a4f4a45 100644
--- a/third_party/blink/web_tests/external/wpt/ua-client-hints/useragentdata.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/ua-client-hints/useragentdata.https.any.js
@@ -62,10 +62,10 @@
 promise_test(() => {
   return navigator.userAgentData.getHighEntropyValues(["platformVersion", "wow64"]).then(
     hints => {
-      if (navigator.userAgentData.platform === "Linux") {
+      if (["Fuchsia", "Linux"].includes(navigator.userAgentData.platform)) {
         assert_true(hints.platformVersion === "");
         assert_equals(hints.wow64, false);
       }
     }
   );
-}, "Platform version and wow64-ness on Linux should be fixed values");
+}, "Platform version and wow64-ness on Fuchsia and Linux should be fixed values");
diff --git a/third_party/blink/web_tests/external/wpt/workers/WorkerNavigator_userAgentData.https.html b/third_party/blink/web_tests/external/wpt/workers/WorkerNavigator_userAgentData.https.html
index a46c530..8ca73df 100644
--- a/third_party/blink/web_tests/external/wpt/workers/WorkerNavigator_userAgentData.https.html
+++ b/third_party/blink/web_tests/external/wpt/workers/WorkerNavigator_userAgentData.https.html
@@ -45,4 +45,21 @@
     // Architecture should be one of two permitted values.
     assert_true(["x86", "arm"].some(arch => arch == e.data.architecture))
   }, "Test that userAgentData is available in workers in secure contexts");
+
+  promise_test(async () => {
+    const e = await new Promise((resolve) => {
+      const worker = new Worker("./support/WorkerNavigator.js");
+      worker.onmessage = resolve;
+    });
+    const highEntropyValues = await navigator.userAgentData.getHighEntropyValues([
+      "platformVersion", "wow64"
+    ]);
+
+    if (["Fuchsia", "Linux"].includes(navigator.userAgentData.platform)) {
+      assert_true(e.data.platformVersion === "");
+      assert_equals(e.data.wow64, false);
+      assert_true(highEntropyValues.platformVersion === "");
+      assert_equals(highEntropyValues.wow64, false);
+    }
+  }, "Platform version and wow64-ness on Linux and Fuchsia should contain fixed values");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/workers/WorkerNavigator_userAgentData.https.tentative.html b/third_party/blink/web_tests/external/wpt/workers/WorkerNavigator_userAgentData.https.tentative.html
deleted file mode 100644
index e4026e2f..0000000
--- a/third_party/blink/web_tests/external/wpt/workers/WorkerNavigator_userAgentData.https.tentative.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<!DOCTYPE html>
-<title> WorkerNavigator.userAgentData </title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-  promise_test(async () => {
-    const e = await new Promise((resolve) => {
-      const worker = new Worker("./support/WorkerNavigator.js");
-      worker.onmessage = resolve;
-    });
-    const highEntropyValues = await navigator.userAgentData.getHighEntropyValues([
-      "platformVersion", "wow64"
-    ]);
-
-    if (navigator.userAgentData.platform === "Linux") {
-      assert_true(e.data.platformVersion === "");
-      assert_equals(e.data.wow64, false);
-      assert_true(highEntropyValues.platformVersion === "");
-      assert_equals(highEntropyValues.wow64, false);
-    }
-  }, "Platform version and wow64-ness on Linux should contain fixed values");
-</script>
diff --git a/third_party/blink/web_tests/fast/css-grid-layout/resources/grid-definitions-parsing-utils.js b/third_party/blink/web_tests/fast/css-grid-layout/resources/grid-definitions-parsing-utils.js
index c1f1680..3872ccf 100644
--- a/third_party/blink/web_tests/fast/css-grid-layout/resources/grid-definitions-parsing-utils.js
+++ b/third_party/blink/web_tests/fast/css-grid-layout/resources/grid-definitions-parsing-utils.js
@@ -22,7 +22,7 @@
     document.body.appendChild(element);
     element.style.display = "grid-lanes";
     if (isRowDirection) {
-        element.style.masonryDirection = "row";
+        element.style.gridLanesDirection = "row";
     }
     element.style.width = "800px";
     element.style.height = "600px";
diff --git a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt
index 5366541..29e86421 100644
--- a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt
+++ b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt
@@ -214,6 +214,7 @@
 grid-auto-rows: auto
 grid-column-end: auto
 grid-column-start: auto
+grid-lanes-direction: column
 grid-lanes-fill: normal
 grid-row-end: auto
 grid-row-start: auto
@@ -269,7 +270,6 @@
 mask-repeat: repeat
 mask-size: auto
 mask-type: luminance
-masonry-direction: column
 math-depth: 0
 math-shift: normal
 math-style: normal
diff --git a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
index e562d6b..d7a3c22e1 100644
--- a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
+++ b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
@@ -214,6 +214,7 @@
 grid-auto-rows: auto
 grid-column-end: auto
 grid-column-start: auto
+grid-lanes-direction: column
 grid-lanes-fill: normal
 grid-row-end: auto
 grid-row-start: auto
@@ -269,7 +270,6 @@
 mask-repeat: repeat
 mask-size: auto
 mask-type: luminance
-masonry-direction: column
 math-depth: 0
 math-shift: normal
 math-style: normal
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes-area-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes-area-expected.txt
index 2a2e9ca..fd1e912f 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes-area-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes-area-expected.txt
@@ -92,7 +92,7 @@
     "isKeyboardFocusable": false,
     "accessibleName": "",
     "accessibleRole": "generic",
-    "layoutObjectName": "LayoutMasonry",
+    "layoutObjectName": "LayoutGridLanes",
     "showAccessibilityInfo": true
   },
   "gridInfo": [
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes-direction-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes-direction-expected.txt
index b225214..53ed3b92 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes-direction-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes-direction-expected.txt
@@ -93,7 +93,7 @@
     "isKeyboardFocusable": false,
     "accessibleName": "",
     "accessibleRole": "generic",
-    "layoutObjectName": "LayoutMasonry",
+    "layoutObjectName": "LayoutGridLanes",
     "showAccessibilityInfo": true
   },
   "gridInfo": [
@@ -311,7 +311,7 @@
     "isKeyboardFocusable": false,
     "accessibleName": "",
     "accessibleRole": "generic",
-    "layoutObjectName": "LayoutMasonry",
+    "layoutObjectName": "LayoutGridLanes",
     "showAccessibilityInfo": true
   },
   "gridInfo": [
@@ -529,7 +529,7 @@
     "isKeyboardFocusable": false,
     "accessibleName": "",
     "accessibleRole": "generic",
-    "layoutObjectName": "LayoutMasonry",
+    "layoutObjectName": "LayoutGridLanes",
     "showAccessibilityInfo": true
   },
   "gridInfo": [
@@ -753,7 +753,7 @@
     "isKeyboardFocusable": false,
     "accessibleName": "",
     "accessibleRole": "generic",
-    "layoutObjectName": "LayoutMasonry",
+    "layoutObjectName": "LayoutGridLanes",
     "showAccessibilityInfo": true
   },
   "gridInfo": [
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes-expected.txt
index dbd3e64f..5ce6aab1 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes-expected.txt
@@ -93,7 +93,7 @@
     "isKeyboardFocusable": false,
     "accessibleName": "",
     "accessibleRole": "generic",
-    "layoutObjectName": "LayoutMasonry",
+    "layoutObjectName": "LayoutGridLanes",
     "showAccessibilityInfo": true
   },
   "gridInfo": [
@@ -356,7 +356,7 @@
     "isKeyboardFocusable": false,
     "accessibleName": "",
     "accessibleRole": "generic",
-    "layoutObjectName": "LayoutMasonry",
+    "layoutObjectName": "LayoutGridLanes",
     "showAccessibilityInfo": true
   },
   "gridInfo": [
@@ -697,7 +697,7 @@
     "isKeyboardFocusable": false,
     "accessibleName": "",
     "accessibleRole": "generic",
-    "layoutObjectName": "LayoutMasonry",
+    "layoutObjectName": "LayoutGridLanes",
     "showAccessibilityInfo": true
   },
   "gridInfo": [
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes.js b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes.js
index 5e02bdaa..6f2d4a4 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes.js
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-lanes.js
@@ -24,7 +24,7 @@
       .grid-lanes-rows {
           display: grid-lanes;
           grid-template-rows: 100px repeat(2, 30px 10%);
-          masonry-direction: row;
+          grid-lanes-direction: row;
           height: 500px;
           width: 300px;
           gap: 20px;
diff --git a/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt b/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt
index 08a1aaf..61a0391 100644
--- a/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt
+++ b/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt
@@ -214,6 +214,7 @@
 grid-auto-rows: auto
 grid-column-end: auto
 grid-column-start: auto
+grid-lanes-direction: column
 grid-lanes-fill: normal
 grid-row-end: auto
 grid-row-start: auto
@@ -269,7 +270,6 @@
 mask-repeat: repeat
 mask-size: auto
 mask-type: luminance
-masonry-direction: column
 math-depth: 0
 math-shift: normal
 math-style: normal
diff --git a/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt b/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
index abff017..3b5a7cc 100644
--- a/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
+++ b/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
@@ -253,6 +253,7 @@
 gridColumnStart
 gridGap
 gridLanes
+gridLanesDirection
 gridLanesFill
 gridLanesFlow
 gridRow
@@ -328,7 +329,6 @@
 maskRepeat
 maskSize
 maskType
-masonryDirection
 mathDepth
 mathShift
 mathStyle
diff --git a/third_party/blink/web_tests/webexposed/css-property-listing-expected.txt b/third_party/blink/web_tests/webexposed/css-property-listing-expected.txt
index 01ad9d6e..926bd373 100644
--- a/third_party/blink/web_tests/webexposed/css-property-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/css-property-listing-expected.txt
@@ -229,6 +229,7 @@
     grid-auto-rows
     grid-column-end
     grid-column-start
+    grid-lanes-direction
     grid-lanes-fill
     grid-row-end
     grid-row-start
@@ -283,7 +284,6 @@
     mask-repeat
     mask-size
     mask-type
-    masonry-direction
     math-depth
     math-shift
     math-style
@@ -763,13 +763,13 @@
         grid-column-end
         grid-column-start
     grid-lanes
+        grid-lanes-direction
         grid-lanes-fill
         grid-template-areas
         grid-template-columns
-        masonry-direction
     grid-lanes-flow
+        grid-lanes-direction
         grid-lanes-fill
-        masonry-direction
     grid-row
         grid-row-end
         grid-row-start
diff --git a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
index 5614f45..8b0f1c1 100644
--- a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
+++ b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
@@ -63,6 +63,7 @@
 local-fonts
 local-network-access
 magnetometer
+manual-text
 media-playback-while-not-visible
 microphone
 midi
diff --git a/third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/permissions_policy.https.html b/third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/permissions_policy.https.html
index e43f6ac..fefd7f6 100644
--- a/third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/permissions_policy.https.html
+++ b/third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/permissions_policy.https.html
@@ -74,6 +74,7 @@
   'local-fonts',
   'local-network-access',
   'magnetometer',
+  'manual-text',
   'media-playback-while-not-visible',
   'microphone',
   'midi',
diff --git a/third_party/boringssl/src b/third_party/boringssl/src
index cb6da2b..3e8ad4e 160000
--- a/third_party/boringssl/src
+++ b/third_party/boringssl/src
@@ -1 +1 @@
-Subproject commit cb6da2b637b89a289403a08f3c5d8d4f75b9227c
+Subproject commit 3e8ad4eb8421e3c76530123a0a98b1cd175dae76
diff --git a/third_party/compiler-rt/src b/third_party/compiler-rt/src
index 90d04a8..4ff2d7f 160000
--- a/third_party/compiler-rt/src
+++ b/third_party/compiler-rt/src
@@ -1 +1 @@
-Subproject commit 90d04a8f70c0d92a9a436fc9706c4db5675d7dd3
+Subproject commit 4ff2d7fc52769a0e233abcf1298cb090b86b59a2
diff --git a/third_party/crossbench b/third_party/crossbench
index 3e62914..8a1fc2c 160000
--- a/third_party/crossbench
+++ b/third_party/crossbench
@@ -1 +1 @@
-Subproject commit 3e62914554aedf28633147fdaa9ff1a2023bc544
+Subproject commit 8a1fc2cc14665e17a3fae3452da9efad18238ed8
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index f15d982..10522a4 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit f15d982497b84034c15b4b33d039a84cdef53ccf
+Subproject commit 10522a4c85f86d03336d2e4fb3b75fd49a35bd46
diff --git a/third_party/glslang/src b/third_party/glslang/src
index 6cfcfaf..aea55ab 160000
--- a/third_party/glslang/src
+++ b/third_party/glslang/src
@@ -1 +1 @@
-Subproject commit 6cfcfaf1985a765c1691f12d413a2fd2945e918f
+Subproject commit aea55ab8e770e1403374e5cea2266b3223300f71
diff --git a/third_party/libpng/OWNERS b/third_party/libpng/OWNERS
index c7c3ece..b33786df 100644
--- a/third_party/libpng/OWNERS
+++ b/third_party/libpng/OWNERS
@@ -1,10 +1,16 @@
-# Current owners - working on removing `libpng` dependency from `chrome`.
-#
-# TODO(https://crbug.com/443128323): Once the `chrome` => ... => `libpng`
-# dependency is removed, we should probably change the `OWNERS` (to owners of
-# //build/android or tests targets that depend on `libpng`?).
-fmalita@chromium.org # Skia
-lukasza@chromium.org # Chrome Security
+# Product code (e.g. Chrome) does **not** depend on `libpng`
+# (see https://crbug.com/443128323).
 
-# Previous owner.
-cblume@chromium.org
+# `//build/config/android` depends on `//third_party/libwebp:cwebp` (see
+# handling of `png_to_webp` in `//build/config/android/internal_rules.gni`).
+# `cwebp` depends on `libpng`.  Therefore `//build/android/OWNERS` end up
+# transitively owning `libpng`.
+file://build/android/OWNERS
+
+# `//third_party/weston` is used by `//ui/ozone/platform/wayland` (in test-only
+# configurations - e.g. see `use_bundled_weston` which
+# `//docs/ozone_overview.md` says may be needed, because "Running some test
+# suites requires a Wayland server").  And `//third_party/weston` depends on
+# `//third_party/libpng` through `//third_party/weston:cairo_shared`.
+# Therefore `.../wayland/OWNERS` end up transitively owning `libpng`.
+file://ui/ozone/platform/wayland/OWNERS
diff --git a/third_party/node/update_npm_deps b/third_party/node/update_npm_deps
index 518720a..ee4a76a7 100755
--- a/third_party/node/update_npm_deps
+++ b/third_party/node/update_npm_deps
@@ -56,7 +56,7 @@
 rm -rf node_modules
 
 echo 'Step 1: Downloading dependencies listed in packages.json, using npm...'
-npm install --no-bin-links --only=prod
+npm install --no-bin-links --no-fund --ignore-scripts --omit=dev --omit=optional
 
 echo 'Step 2: Downloading dependencies not listed in packages.json, manually...'
 ./download_npm_deps_manually.sh
diff --git a/third_party/skia b/third_party/skia
index cba3ac5..156de4c 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit cba3ac5c7ab064bf435a44715d29dab39e052243
+Subproject commit 156de4c06c2719e4d6bb3929a0dad88c250fc3d8
diff --git a/third_party/spirv-tools/src b/third_party/spirv-tools/src
index 1860788..971a7b6 160000
--- a/third_party/spirv-tools/src
+++ b/third_party/spirv-tools/src
@@ -1 +1 @@
-Subproject commit 186078866b88db9a91ef985b98f666c29241a869
+Subproject commit 971a7b6e8d7740035bbff089bbbf9f42951ecfd5
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps
index bc49ded..0803a73 160000
--- a/third_party/vulkan-deps
+++ b/third_party/vulkan-deps
@@ -1 +1 @@
-Subproject commit bc49dedd8bfece8b76a44145a432817f2ea259b5
+Subproject commit 0803a732819f5ee1550d0dd014060560cdf87ce1
diff --git a/third_party/vulkan-loader/src b/third_party/vulkan-loader/src
index 65e5428..5f6d4be 160000
--- a/third_party/vulkan-loader/src
+++ b/third_party/vulkan-loader/src
@@ -1 +1 @@
-Subproject commit 65e5428032e4ccb2e064fa8691b96e61701d5956
+Subproject commit 5f6d4be882cb015228c994f81098220dada056b7
diff --git a/third_party/vulkan-validation-layers/src b/third_party/vulkan-validation-layers/src
index c1054db..e114bc2 160000
--- a/third_party/vulkan-validation-layers/src
+++ b/third_party/vulkan-validation-layers/src
@@ -1 +1 @@
-Subproject commit c1054dbc91e22cb6d99bf5b0766c4efb18dc2bd6
+Subproject commit e114bc2b59dadb4c07eb8cff7873c6a4b366543b
diff --git a/third_party/webrtc b/third_party/webrtc
index 89e8619..ff73efd 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit 89e8619bfdacfd1364bf3fc6bc5388c92629ab71
+Subproject commit ff73efd929c8b8362a28dfe6708bf16d0fa34c2b
diff --git a/tools/crates/run_cargo.py b/tools/crates/run_cargo.py
index 3dcc28da..b4fd12b 100755
--- a/tools/crates/run_cargo.py
+++ b/tools/crates/run_cargo.py
@@ -78,12 +78,12 @@
     return chr(0x1f).join(flags)
 
 
-def _AppendOrInsert(dict, key, sep, value):
+def _PrependOrInsert(dict, key, sep, value):
     """Insert `value` into `dict[key]` if it doesn't already exist,
-       otherwise append `sep + value` to the existing entry
+       otherwise prepend `sep + value` to the existing entry
     """
     if key in dict:
-        dict[key] += sep + value
+        dict[key] = value + sep + dict[key]
     else:
         dict[key] = value
 
@@ -99,9 +99,9 @@
     if home_dir:
         cargo_env['CARGO_HOME'] = home_dir
 
-    _AppendOrInsert(cargo_env, 'PATH', os.pathsep, str(bin_dir))
-    _AppendOrInsert(cargo_env, 'CARGO_ENCODED_RUSTFLAGS', chr(0x1f),
-                    _GetWindowsToolchainFlags())
+    _PrependOrInsert(cargo_env, 'PATH', os.pathsep, str(bin_dir))
+    _PrependOrInsert(cargo_env, 'CARGO_ENCODED_RUSTFLAGS', chr(0x1f),
+                     _GetWindowsToolchainFlags())
 
     # https://docs.python.org/3/library/subprocess.html#subprocess.Popen:
     #     **Warning**: For maximum reliability, use a fully qualified path for
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index a5431ae..d6b9e09 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -5591,6 +5591,17 @@
   </description>
   <token key="ActionType">
     <variant name="" summary="aggregated"/>
+    <variant name=".AffirmTosScreen.Accepted"
+        summary="BNPL ToS screen for Affirm accepted."/>
+    <variant name=".AffirmTosScreen.Dismissed"
+        summary="BNPL ToS screen for Affirm dismissed."/>
+    <variant name=".AffirmTosScreen.LegalMessageLinkClicked"
+        summary="Link in legal messages on BNPL ToS screen for Affirm was
+                 clicked."/>
+    <variant name=".AffirmTosScreen.Shown"
+        summary="BNPL ToS screen shown for Affirm."/>
+    <variant name=".AffirmTosScreen.WalletLinkClicked"
+        summary="Wallet link on BNPL ToS screen for Affirm was clicked."/>
     <variant name=".ErrorScreen.Dismissed" summary="Error screen dismissed."/>
     <variant name=".ErrorScreen.Shown" summary="Error screen shown."/>
     <variant name=".IssuerSelectionScreen.AffirmLinkedSelected"
@@ -5613,9 +5624,31 @@
         summary="Linked Zip issuer selected."/>
     <variant name=".IssuerSelectionScreen.ZipUnlinkedSelected"
         summary="Unlinked Zip issuer selected."/>
+    <variant name=".KlarnaTosScreen.Accepted"
+        summary="BNPL ToS screen for Klarna accepted."/>
+    <variant name=".KlarnaTosScreen.Dismissed"
+        summary="BNPL ToS screen for Klarna dismissed."/>
+    <variant name=".KlarnaTosScreen.LegalMessageLinkClicked"
+        summary="Link in legal messages on BNPL ToS screen for Klarna was
+                 clicked."/>
+    <variant name=".KlarnaTosScreen.Shown"
+        summary="BNPL ToS screen shown for Klarna."/>
+    <variant name=".KlarnaTosScreen.WalletLinkClicked"
+        summary="Wallet link on BNPL ToS screen for Klarna was clicked."/>
     <variant name=".ProgressScreen.Dismissed"
         summary="Progress screen dismissed."/>
     <variant name=".ProgressScreen.Shown" summary="Progress screen shown."/>
+    <variant name=".ZipTosScreen.Accepted"
+        summary="BNPL ToS screen for Zip accepted."/>
+    <variant name=".ZipTosScreen.Dismissed"
+        summary="BNPL ToS screen for Zip dismissed."/>
+    <variant name=".ZipTosScreen.LegalMessageLinkClicked"
+        summary="Link in legal messages on BNPL ToS screen for Zip was
+                 clicked."/>
+    <variant name=".ZipTosScreen.Shown"
+        summary="BNPL ToS screen shown for Zip."/>
+    <variant name=".ZipTosScreen.WalletLinkClicked"
+        summary="Wallet link on BNPL ToS screen for Zip was clicked."/>
   </token>
 </action>
 
@@ -47228,6 +47261,15 @@
   </description>
 </action>
 
+<action name="TabContextMenu_AddToClosedSavedGroup">
+  <owner>dominicaustria@google.com</owner>
+  <owner>top-chrome-desktop-ui@google.com</owner>
+  <description>
+    User selected the menu item to add to a closed saved tab group from the tab
+    context menu.
+  </description>
+</action>
+
 <action name="TabContextMenu_AddToExistingGroup">
   <owner>dpenning@chromium.org</owner>
   <owner>top-chrome-desktop-ui@google.com</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index e043673..6071b3b 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -289,6 +289,24 @@
   <int value="2" label="Timeout"/>
 </enum>
 
+<enum name="AndroidApiLevel">
+  <int value="16" label="JELLY_BEAN"/>
+  <int value="17" label="JELLY_BEAN_MR1"/>
+  <int value="18" label="JELLY_BEAN_MR2"/>
+  <int value="19" label="KITKAT"/>
+  <int value="20" label="KITKAT_WATCH"/>
+  <int value="21" label="LOLLIPOP"/>
+  <int value="22" label="LOLLIPOP_MR1"/>
+  <int value="23" label="M"/>
+  <int value="24" label="N"/>
+  <int value="25" label="N_MR1"/>
+  <int value="26" label="O"/>
+  <int value="27" label="O_MR1"/>
+  <int value="28" label="P"/>
+  <int value="29" label="Q"/>
+  <int value="30" label="R"/>
+</enum>
+
 <enum name="AndroidMultiWindowActivityType">
   <int value="0" label="Enter"/>
   <int value="1" label="Exit"/>
@@ -9249,6 +9267,7 @@
   <int value="-1724238554"
       label="AutofillEnableLogFormEventsToAllParsedFormTypes:enabled"/>
   <int value="-1723847451" label="EnableChromeRefreshTokenBinding:enabled"/>
+  <int value="-1723531810" label="TabGroupsFocusing:enabled"/>
   <int value="-1723086446"
       label="AccessibilityMagnifyAcceleratorDialog:enabled"/>
   <int value="-1722208902" label="CCTModuleCustomRequestHeader:disabled"/>
@@ -16941,6 +16960,7 @@
   <int value="970901442" label="EnableAndroidGamepadVibration:enabled"/>
   <int value="972228058" label="SyncUSSSessions:disabled"/>
   <int value="973601997" label="SafeBrowsingUseLocalBlacklistsV2:disabled"/>
+  <int value="974840558" label="TabGroupsFocusing:disabled"/>
   <int value="975104092" label="show-taps"/>
   <int value="975249239" label="PasswordManagerRedesign:enabled"/>
   <int value="975463471" label="WebViewExtraHeadersSameOriginOnly:enabled"/>
diff --git a/tools/metrics/histograms/metadata/actor/histograms.xml b/tools/metrics/histograms/metadata/actor/histograms.xml
index 99ff2bc..9b7d5b9e 100644
--- a/tools/metrics/histograms/metadata/actor/histograms.xml
+++ b/tools/metrics/histograms/metadata/actor/histograms.xml
@@ -282,56 +282,6 @@
   </summary>
 </histogram>
 
-<histogram name="Actor.ObservationDelay.DidTimeout" enum="Boolean"
-    expires_after="2026-08-11">
-  <owner>linnan@google.com</owner>
-  <owner>bokan@google.com</owner>
-  <owner>chrome-synapse-team@google.com</owner>
-  <summary>
-    Records whether the ObservationDelayController's Wait() timed out. Recorded
-    when the observation delay is completed.
-  </summary>
-</histogram>
-
-<histogram name="Actor.ObservationDelay.LcpDelayNeeded" enum="Boolean"
-    expires_after="2026-08-11">
-  <owner>linnan@google.com</owner>
-  <owner>bokan@google.com</owner>
-  <owner>chrome-synapse-team@google.com</owner>
-  <summary>
-    Records whether an additional delay was added by the
-    ObservationDelayController because the Largest Contentful Paint (LCP) had
-    not yet occurred. Not recorded for timeouts.
-  </summary>
-</histogram>
-
-<histogram name="Actor.ObservationDelay.StateDuration.{State}" units="ms"
-    expires_after="2026-08-11">
-  <owner>linnan@google.com</owner>
-  <owner>bokan@google.com</owner>
-  <owner>chrome-synapse-team@google.com</owner>
-  <summary>
-    Measures the duration of {State} in the ObservationDelayController's state
-    machine. Not recorded for timeouts.
-  </summary>
-  <token key="State">
-    <variant name="WaitForLoadCompletion"/>
-    <variant name="WaitForPageStability"/>
-    <variant name="WaitForVisualStateUpdate"/>
-  </token>
-</histogram>
-
-<histogram name="Actor.ObservationDelay.TotalWaitDuration" units="ms"
-    expires_after="2026-08-11">
-  <owner>linnan@google.com</owner>
-  <owner>bokan@google.com</owner>
-  <owner>chrome-synapse-team@google.com</owner>
-  <summary>
-    Measures the total time from when Wait() is called until the observation
-    delay completes. Not recorded for timeouts.
-  </summary>
-</histogram>
-
 <histogram name="Actor.PageContext.APC.Duration" units="ms"
     expires_after="2026-08-11">
   <owner>dtapuska@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/android/enums.xml b/tools/metrics/histograms/metadata/android/enums.xml
index 4f0a64c..44f64e94 100644
--- a/tools/metrics/histograms/metadata/android/enums.xml
+++ b/tools/metrics/histograms/metadata/android/enums.xml
@@ -74,24 +74,6 @@
   <int value="8" label="onLowMemory()"/>
 </enum>
 
-<enum name="AndroidApiLevel">
-  <int value="16" label="JELLY_BEAN"/>
-  <int value="17" label="JELLY_BEAN_MR1"/>
-  <int value="18" label="JELLY_BEAN_MR2"/>
-  <int value="19" label="KITKAT"/>
-  <int value="20" label="KITKAT_WATCH"/>
-  <int value="21" label="LOLLIPOP"/>
-  <int value="22" label="LOLLIPOP_MR1"/>
-  <int value="23" label="M"/>
-  <int value="24" label="N"/>
-  <int value="25" label="N_MR1"/>
-  <int value="26" label="O"/>
-  <int value="27" label="O_MR1"/>
-  <int value="28" label="P"/>
-  <int value="29" label="Q"/>
-  <int value="30" label="R"/>
-</enum>
-
 <enum name="AndroidAutoDarkModeSettingsChangeSource">
   <summary>See Android.DarkTheme.AutoDarkMode.SettingsChangeSource.*</summary>
   <int value="0" label="Theme settings"/>
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index bde3298..43ed4b1 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -399,6 +399,7 @@
   <variant name="HandleCaretMovedInFormField"/>
   <variant name="JavaScriptChangedValue"/>
   <variant name="NotifyPasswordManagerAboutClearedForm"/>
+  <variant name="OnDevToolsSessionConnectionChanged"/>
   <variant name="OnProvisionallySaveForm"/>
   <variant name="OnTextFieldValueChanged"/>
   <variant name="QueryAutofillSuggestions"/>
diff --git a/tools/metrics/histograms/metadata/blink/enums.xml b/tools/metrics/histograms/metadata/blink/enums.xml
index fb86730..578b404 100644
--- a/tools/metrics/histograms/metadata/blink/enums.xml
+++ b/tools/metrics/histograms/metadata/blink/enums.xml
@@ -6515,6 +6515,7 @@
   <int value="140" label="AriaNotify"/>
   <int value="141" label="DigitalCredentialsCreate"/>
   <int value="142" label="MulticastInDirectSockets"/>
+  <int value="143" label="ManualText"/>
 </enum>
 
 <!-- LINT.IfChange(FedCmAccountChooserResult) -->
@@ -8083,7 +8084,7 @@
   <int value="810" label="view-transition-capture-mode (obsolete)"/>
   <int value="811" label="interactivity"/>
   <int value="812" label="grid-lanes-fill"/>
-  <int value="813" label="masonry-direction"/>
+  <int value="813" label="grid-lanes-direction"/>
   <int value="814" label="grid-lanes-flow"/>
   <int value="815" label="masonry-auto-tracks (obsolete)"/>
   <int value="816" label="result"/>
@@ -8600,7 +8601,7 @@
   <int value="305" label="AppShortcuts"/>
   <int value="306" label="Fetchlater"/>
   <int value="307" label="DRAFT_WasmBranchHinting"/>
-  <int value="308" label="DRAFT_ExplicitResourceManagement"/>
+  <int value="308" label="ExplicitResourceManagement"/>
   <int value="309" label="AppLaunchHandler"/>
   <int value="310" label="Function"/>
   <int value="311" label="If"/>
diff --git a/tools/metrics/histograms/metadata/browser/enums.xml b/tools/metrics/histograms/metadata/browser/enums.xml
index 43d43b4..007aa3eb 100644
--- a/tools/metrics/histograms/metadata/browser/enums.xml
+++ b/tools/metrics/histograms/metadata/browser/enums.xml
@@ -349,7 +349,8 @@
   <int value="116"
       label="BIDDING_AND_AUCTION_USER_CONSENTED_DEBUGGING_DELEGATE"/>
   <int value="117" label="PARCEL_TRACKING_INFOBAR_DELEGATE (Obsolete)"/>
-  <int value="118" label="TEST_THIRD_PARTY_COOKIE_PHASEOUT_DELEGATE"/>
+  <int value="118"
+      label="TEST_THIRD_PARTY_COOKIE_PHASEOUT_DELEGATE (Obsolete)"/>
   <int value="119" label="ENABLE_LINK_CAPTURING_INFOBAR_DELEGATE"/>
   <int value="120" label="DEV_TOOLS_SHARED_PROCESS_DELEGATE"/>
   <int value="121" label="ENHANCED_SAFE_BROWSING_INFOBAR_DELEGATE"/>
diff --git a/tools/metrics/histograms/metadata/enterprise/histograms.xml b/tools/metrics/histograms/metadata/enterprise/histograms.xml
index 6e0f0794..a9919d0 100644
--- a/tools/metrics/histograms/metadata/enterprise/histograms.xml
+++ b/tools/metrics/histograms/metadata/enterprise/histograms.xml
@@ -378,7 +378,7 @@
 </histogram>
 
 <histogram name="Enterprise.CBCMPolicyInvalidations"
-    enum="EnterprisePolicyInvalidations" expires_after="2026-04-26">
+    enum="EnterprisePolicyInvalidations" expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>cbe-eng@google.com</owner>
   <summary>
@@ -390,7 +390,7 @@
 </histogram>
 
 <histogram name="Enterprise.CBCMPolicyRefresh" enum="EnterprisePolicyRefresh"
-    expires_after="2026-04-26">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>cbe-eng@google.com</owner>
   <summary>
@@ -401,7 +401,7 @@
 </histogram>
 
 <histogram name="Enterprise.CBCMRealTimeReportEnqueue"
-    enum="EnterpriseCloudReportingStatusCode" expires_after="2025-12-20">
+    enum="EnterpriseCloudReportingStatusCode" expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>src/chrome/browser/enterprise/reporting/OWNERS</owner>
   <summary>
@@ -574,7 +574,7 @@
 </histogram>
 
 <histogram name="Enterprise.CloudExtensionRequestUpdated"
-    enum="EnterpriseCloudExtensionRequestListUpdate" expires_after="2025-12-01">
+    enum="EnterpriseCloudExtensionRequestListUpdate" expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>src/chrome/browser/enterprise/reporting/OWNERS</owner>
   <summary>
@@ -585,7 +585,7 @@
 </histogram>
 
 <histogram name="Enterprise.CloudManagement.PolicyFetchingTime" units="ms"
-    expires_after="2026-04-05">
+    expires_after="2026-12-01">
   <owner>ftirelo@chromium.org</owner>
   <owner>zmin@chromium.org</owner>
   <summary>
@@ -596,7 +596,7 @@
 
 <histogram name="Enterprise.CloudManagementEnrollmentTokenLocation.Mac"
     enum="EnterpriseCloudManagementEnrollmentTokenLocationMac"
-    expires_after="2026-10-01">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>igorruvinov@chromium.org</owner>
   <summary>
@@ -605,7 +605,7 @@
 </histogram>
 
 <histogram name="Enterprise.CloudReportingBasicRequestSize" units="KB"
-    expires_after="2026-04-12">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -616,7 +616,7 @@
 </histogram>
 
 <histogram name="Enterprise.CloudReportingRequestCount" units="requests"
-    expires_after="2026-04-05">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -626,7 +626,7 @@
 </histogram>
 
 <histogram name="Enterprise.CloudReportingRequestSize" units="KB"
-    expires_after="2026-04-05">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -636,7 +636,7 @@
 </histogram>
 
 <histogram name="Enterprise.CloudReportingResponse"
-    enum="EnterpriseCloudReportingResponse" expires_after="2026-04-05">
+    enum="EnterpriseCloudReportingResponse" expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -1000,7 +1000,7 @@
 </histogram>
 
 <histogram name="Enterprise.DeviceSignals.Collection.{Variant}"
-    enum="DeviceSignalsSignalName" expires_after="2026-05-24">
+    enum="DeviceSignalsSignalName" expires_after="2026-02-10">
   <owner>seblalancette@chromium.org</owner>
   <owner>cbe-device-trust-eng@google.com</owner>
   <summary>
@@ -1744,7 +1744,7 @@
 </histogram>
 
 <histogram name="Enterprise.Dlp.ReportedBlockLevelRestriction"
-    enum="EnterpriseDlpPolicyRestriction" expires_after="2026-05-24">
+    enum="EnterpriseDlpPolicyRestriction" expires_after="2026-03-22">
   <owner>poromov@chromium.org</owner>
   <owner>chromeos-dlp@google.com</owner>
   <summary>
@@ -1754,7 +1754,7 @@
 </histogram>
 
 <histogram name="Enterprise.Dlp.ReportedEventStatus" enum="GoogleRpcCode"
-    expires_after="2026-05-24">
+    expires_after="2026-03-22">
   <owner>poromov@chromium.org</owner>
   <owner>chromeos-dlp@google.com</owner>
   <summary>
@@ -1763,7 +1763,7 @@
 </histogram>
 
 <histogram name="Enterprise.Dlp.ReportedReportLevelRestriction"
-    enum="EnterpriseDlpPolicyRestriction" expires_after="2026-05-24">
+    enum="EnterpriseDlpPolicyRestriction" expires_after="2026-03-22">
   <owner>poromov@chromium.org</owner>
   <owner>chromeos-dlp@google.com</owner>
   <summary>
@@ -1930,7 +1930,7 @@
 
 <histogram
     name="Enterprise.DMServerCloudPolicyRequestStatus{EnterpriseDMServerCloudPolicyRequest}"
-    enum="EnterpriseDeviceManagementStatus" expires_after="2026-04-26">
+    enum="EnterpriseDeviceManagementStatus" expires_after="2026-12-01">
   <owner>vincb@google.com</owner>
   <owner>zmin@chromium.org</owner>
   <summary>
@@ -1949,7 +1949,7 @@
 </histogram>
 
 <histogram name="Enterprise.DMServerRequestSuccess{EnterpriseDMServerRequest}"
-    enum="EnterpriseDMServerRequestSuccess" expires_after="2026-05-24">
+    enum="EnterpriseDMServerRequestSuccess" expires_after="2026-12-01">
   <owner>rbock@google.com</owner>
   <owner>chromeos-commercial-remote-management@google.com</owner>
   <owner>managed-devices@google.com</owner>
@@ -2409,7 +2409,7 @@
 </histogram>
 
 <histogram name="Enterprise.FileAnalysisRequest.FileSize" units="KB"
-    expires_after="2026-05-24">
+    expires_after="2026-03-22">
   <owner>eliashomsi@google.com</owner>
   <owner>nancylanxiao@google.com</owner>
   <owner>cbe-cep-eng@google.com</owner>
@@ -2623,7 +2623,7 @@
 
 <histogram
     name="Enterprise.MachineLevelUserCloudPolicyEnrollment.RequestSuccessTime"
-    units="ms" expires_after="2025-12-01">
+    units="ms" expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -2634,7 +2634,7 @@
 
 <histogram name="Enterprise.MachineLevelUserCloudPolicyEnrollment.Result"
     enum="MachineLevelUserCloudPolicyEnrollmentResult"
-    expires_after="2026-04-05">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>The result of machine level user cloud policy enrollment.</summary>
@@ -2643,7 +2643,7 @@
 <histogram
     name="Enterprise.MachineLevelUserCloudPolicyEnrollment.StartupDialog"
     enum="MachineLevelUserCloudPolicyEnrollmentStartupDialog"
-    expires_after="2026-05-24">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -2654,7 +2654,7 @@
 
 <histogram
     name="Enterprise.MachineLevelUserCloudPolicyEnrollment.StartupDialogTime"
-    units="ms" expires_after="2025-12-01">
+    units="ms" expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -2665,7 +2665,7 @@
 
 <histogram
     name="Enterprise.MachineLevelUserCloudPolicyEnrollment.UnenrollSuccess"
-    enum="BooleanSuccess" expires_after="2025-12-01">
+    enum="BooleanSuccess" expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>domfc@chromium.org</owner>
   <summary>
@@ -2873,7 +2873,7 @@
 </histogram>
 
 <histogram name="Enterprise.Policies.Sources" enum="EnterprisePoliciesSources"
-    expires_after="2026-04-12">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>igorruvinov@chromium.org</owner>
   <owner>chromeos-commercial-remote-management@google.com</owner>
@@ -2887,7 +2887,7 @@
 </histogram>
 
 <histogram name="Enterprise.Policies.{PolicyLevel}" enum="EnterprisePolicies"
-    expires_after="2026-04-26">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>qiutanw@google.com</owner>
   <owner>chromeos-commercial-remote-management@google.com</owner>
@@ -2900,7 +2900,7 @@
 </histogram>
 
 <histogram name="Enterprise.PoliciesEverFetchedWithProfileId" enum="Boolean"
-    expires_after="2026-05-01">
+    expires_after="2026-12-01">
   <owner>nicolaso@chromium.org</owner>
   <owner>zmin@chromium.org</owner>
   <summary>
@@ -2942,7 +2942,7 @@
 </histogram>
 
 <histogram name="Enterprise.PolicyServiceInitTime{Scope}{PolicyCountSuffix}"
-    units="ms" expires_after="2026-04-05">
+    units="ms" expires_after="2026-12-01">
   <owner>ftirelo@chromium.org</owner>
   <owner>zmin@chromium.org</owner>
   <summary>
@@ -2983,7 +2983,7 @@
 </histogram>
 
 <histogram name="Enterprise.PolicyUI.ButtonUsage.{ButtonType}" units="counts"
-    expires_after="2026-04-26">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>src/components/policy/OWNERS</owner>
   <summary>
@@ -3014,7 +3014,7 @@
 </histogram>
 
 <histogram name="Enterprise.PolicyUpdatePeriod.MachineLevelUser" units="days"
-    expires_after="2026-05-24">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -3041,7 +3041,7 @@
 </histogram>
 
 <histogram name="Enterprise.ProfileAffiliation.IsAffiliated" enum="Boolean"
-    expires_after="2026-05-24">
+    expires_after="2026-03-22">
   <owner>igorruvinov@chromium.org</owner>
   <owner>cbe-magic@google.com</owner>
   <summary>
@@ -3051,7 +3051,7 @@
 </histogram>
 
 <histogram name="Enterprise.ProfileAffiliation.UnaffiliatedReason"
-    enum="EnterpriseProfileUnaffiliatedReason" expires_after="2026-05-24">
+    enum="EnterpriseProfileUnaffiliatedReason" expires_after="2026-03-22">
   <owner>igorruvinov@chromium.org</owner>
   <owner>cbe-magic@google.com</owner>
   <summary>
@@ -3105,7 +3105,7 @@
 </histogram>
 
 <histogram name="Enterprise.PublicSession.SessionLength" units="minutes"
-    expires_after="2026-05-24">
+    expires_after="2026-03-22">
   <owner>bfranz@chromium.org</owner>
   <owner>chromeos-kiosk-eng@google.com</owner>
   <summary>
@@ -3129,7 +3129,7 @@
 </histogram>
 
 <histogram name="Enterprise.RegisterCloudPolicyService"
-    enum="RegisterCloudPolicyServiceEvent" expires_after="2026-08-01">
+    enum="RegisterCloudPolicyServiceEvent" expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>ydago@chromium.org</owner>
   <summary>
@@ -3258,7 +3258,7 @@
 
 <histogram name="Enterprise.SecondaryGoogleAccountUsage.PolicyFetch.Status"
     enum="SecondaryGoogleAccountUsagePolicyFetchStatus"
-    expires_after="2026-05-24">
+    expires_after="2026-03-22">
   <owner>rodmartin@google.com</owner>
   <owner>chromeos-commercial-identity@google.com</owner>
   <summary>
@@ -3334,7 +3334,7 @@
 </histogram>
 
 <histogram name="Enterprise.SkyVault.Migration.WriteAccessError" enum="Boolean"
-    expires_after="2026-05-24">
+    expires_after="2026-03-22">
   <owner>aidazolic@google.com</owner>
   <owner>src/chrome/browser/ash/policy/skyvault/OWNERS</owner>
   <summary>
@@ -3814,7 +3814,7 @@
 </histogram>
 
 <histogram name="Enterprise.UserInfoFetch.HttpErrorCode"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2025-12-19">
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2026-12-01">
   <owner>vincb@google.com</owner>
   <owner>zmin@chromium.org</owner>
   <summary>
@@ -3827,7 +3827,7 @@
 </histogram>
 
 <histogram name="Enterprise.UserInfoFetch.Status"
-    enum="EnterpriseUserInfoFetchStatus" expires_after="2025-12-26">
+    enum="EnterpriseUserInfoFetchStatus" expires_after="2026-12-01">
   <owner>vincb@google.com</owner>
   <owner>zmin@chromium.org</owner>
   <summary>
@@ -3966,7 +3966,7 @@
 
 <histogram
     name="Enterprise.{ContentAnalysisProtocol}Request.{ContentAnalysisRequestType}.Result"
-    enum="SafeBrowsingBinaryUploadResult" expires_after="2026-05-24">
+    enum="SafeBrowsingBinaryUploadResult" expires_after="2026-03-22">
   <owner>nancylanxiao@google.com</owner>
   <owner>domfc@chromium.org</owner>
   <owner>cbe-cep-eng@google.com</owner>
@@ -4020,7 +4020,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.AzureADJoinStatusCheckTime" units="ms"
-    expires_after="2026-10-01">
+    expires_after="2026-12-01">
   <owner>igorruvinov@chromium.org</owner>
   <owner>ydago@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
@@ -4032,7 +4032,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.InDomain" enum="BooleanEnabled"
-    expires_after="2025-12-01">
+    expires_after="2026-12-01">
   <owner>ydago@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -4057,7 +4057,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.InvalidPolicies" enum="EnterprisePolicies"
-    expires_after="2026-05-17">
+    expires_after="2026-12-01">
   <owner>pastarmovj@chromium.org</owner>
   <owner>ydago@chromium.org</owner>
   <summary>
@@ -4068,7 +4068,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.InvalidPoliciesDetected"
-    units="disabled policies" expires_after="2026-10-01">
+    units="disabled policies" expires_after="2026-12-01">
   <owner>pastarmovj@chromium.org</owner>
   <owner>zmin@chomium.org</owner>
   <summary>
@@ -4079,7 +4079,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.IsDomainJoined" enum="BooleanEnabled"
-    expires_after="2026-04-12">
+    expires_after="2026-12-01">
   <owner>pastarmovj@chromium.org</owner>
   <owner>zmin@chromium.org</owner>
   <summary>
@@ -4089,7 +4089,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.IsEnterpriseUser" enum="BooleanEnabled"
-    expires_after="2026-04-05">
+    expires_after="2026-12-01">
   <owner>pastarmovj@chromium.org</owner>
   <owner>zmin@chromium.org</owner>
   <summary>
@@ -4100,7 +4100,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.IsFullyManaged2" enum="IsFullyManagedBoolean"
-    expires_after="2026-04-05">
+    expires_after="2026-12-01">
   <owner>twellington@google.com</owner>
   <owner>tedchcoc@chromium.org</owner>
   <summary>
@@ -4111,7 +4111,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.IsJoinedToAzureAD" enum="BooleanEnabled"
-    expires_after="2026-10-01">
+    expires_after="2026-12-01">
   <owner>igorruvinov@chromium.org</owner>
   <owner>ydago@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
@@ -4124,7 +4124,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.IsLocalMachine" enum="Boolean"
-    expires_after="2026-04-05">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -4134,7 +4134,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.IsLocalUser" enum="Boolean"
-    expires_after="2026-10-01">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -4145,7 +4145,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.IsManaged2" enum="BooleanEnabled"
-    expires_after="2026-04-05">
+    expires_after="2026-12-01">
   <owner>zmin@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -4158,7 +4158,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.IsManagedOrEnterpriseDevice"
-    enum="BooleanEnabled" expires_after="2026-04-05">
+    enum="BooleanEnabled" expires_after="2026-12-01">
   <owner>pastarmovj@chromium.org</owner>
   <owner>zmin@chromium.org</owner>
   <summary>
@@ -4179,7 +4179,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.Mac.IsCurrentUserDomainUser" enum="Boolean"
-    expires_after="2026-10-01">
+    expires_after="2026-12-01">
   <owner>pastarmovj@chromium.org</owner>
   <owner>zmin@chromium.org</owner>
   <summary>
@@ -4189,7 +4189,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.Mac.IsDeviceDomainJoined" enum="Boolean"
-    expires_after="2026-04-12">
+    expires_after="2026-12-01">
   <owner>pastarmovj@chromium.org</owner>
   <owner>zmin@chromium.org</owner>
   <summary>
@@ -4199,7 +4199,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.Mac.IsDeviceMDMEnrolledNew"
-    enum="EnterpriseMacMDMStatusNew" expires_after="2026-10-01">
+    enum="EnterpriseMacMDMStatusNew" expires_after="2026-12-01">
   <owner>pastarmovj@chromium.org</owner>
   <owner>zmin@chromium.org</owner>
   <summary>
@@ -4210,7 +4210,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.OSType" enum="OsSuite"
-    expires_after="2025-12-01">
+    expires_after="2026-12-01">
   <owner>ydago@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/event/histograms.xml b/tools/metrics/histograms/metadata/event/histograms.xml
index 0565f73..fd91223 100644
--- a/tools/metrics/histograms/metadata/event/histograms.xml
+++ b/tools/metrics/histograms/metadata/event/histograms.xml
@@ -1089,28 +1089,6 @@
   <token key="ScrollEventType" variants="ScrollEventType"/>
 </histogram>
 
-<histogram name="EventLatency.{ScrollEventType}.{DispatchStage}.{RatioType}"
-    units="%" expires_after="2026-05-03">
-  <owner>jonross@chromium.org</owner>
-  <owner>chrome-gpu-metric-alerts@chromium.org</owner>
-  <summary>
-    Tracks the latency of {ScrollEventType}, as a ratio from {DispatchStage} to
-    the VSync time. This is normalized by the frame interval. This is reported
-    when the stage occurs {RatioType}.
-  </summary>
-  <token key="ScrollEventType" variants="ScrollEventType"/>
-  <token key="DispatchStage">
-    <variant name="ArrivedInRendererVsVSyncRatio"
-        summary="when the event arrived in the Renderer"/>
-    <variant name="GenerationVsVsyncRatio"
-        summary="when the event was generated"/>
-  </token>
-  <token key="RatioType">
-    <variant name="AfterVSync" summary="after the VSync timestamp"/>
-    <variant name="BeforeVSync" summary="before the VSync timestamp"/>
-  </token>
-</histogram>
-
 <histogram name="EventLatency.{ScrollEventType}.{TopControls}"
     units="microseconds" expires_after="2026-05-17">
   <owner>jonross@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/gpu/enums.xml b/tools/metrics/histograms/metadata/gpu/enums.xml
index 58471a1..659d772 100644
--- a/tools/metrics/histograms/metadata/gpu/enums.xml
+++ b/tools/metrics/histograms/metadata/gpu/enums.xml
@@ -1033,6 +1033,14 @@
   <int value="1" label="GPU process crash limit reached"/>
 </enum>
 
+<enum name="GpuPersistentCacheLoadResult">
+  <summary>The cache hit/miss results of GpuPersistentCache.</summary>
+  <int value="0" label="Miss"/>
+  <int value="1" label="Miss (disk cache not available)"/>
+  <int value="10" label="Hit (memory)"/>
+  <int value="11" label="Hit (disk)"/>
+</enum>
+
 <enum name="GPUProcessExitCode">
   <summary>
     The exit code returned by the GPU process when it terminated. These match
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index 2412c589..9524b4f8 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -1291,6 +1291,18 @@
   <token key="GraphiteDawnOrWebGPU" variants="GraphiteDawnOrWebGPU"/>
 </histogram>
 
+<histogram name="GPU.PersistentCache.{GraphiteDawnOrWebGPU}.LoadResult"
+    enum="GpuPersistentCacheLoadResult" expires_after="2026-03-22">
+  <owner>geofflang@chromium.org</owner>
+  <owner>chrome-gpu-metric-alerts@chromium.org</owner>
+  <owner>mdb.webgpu-dev-team@google.com</owner>
+  <summary>
+    Records the results of gpu::GpuPersistentCache::Load for determining cache
+    hit rates.
+  </summary>
+  <token key="GraphiteDawnOrWebGPU" variants="GraphiteDawnOrWebGPU"/>
+</histogram>
+
 <histogram name="GPU.PersistentCache.{GraphiteDawnOrWebGPU}.Store"
     units="microseconds" expires_after="2026-05-24">
   <owner>kylechar@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/input/histograms.xml b/tools/metrics/histograms/metadata/input/histograms.xml
index a2e81476..8efb242 100644
--- a/tools/metrics/histograms/metadata/input/histograms.xml
+++ b/tools/metrics/histograms/metadata/input/histograms.xml
@@ -23,9 +23,10 @@
 <histograms>
 
 <histogram name="Input.Digitizer.MaxTouchPoints.{PointerDigitizerType}"
-    units="maximum allowed touch points" expires_after="2025-03-01">
+    units="maximum allowed touch points" expires_after="2027-01-01">
   <owner>kouhei@chromium.org</owner>
-  <owner>Adam.Ettenberger@microsoft.com</owner>
+  <owner>gastonr@microsoft.com</owner>
+  <owner>gerchiko@microsoft.com</owner>
   <owner>input-dev@chromium.org</owner>
   <summary>
     Record the maximum number of active touch points allowed for each pointer
@@ -35,6 +36,8 @@
     initializing the singleton `TouchUiController` during startup, and once for
     each pointer digitizer that's connected afterwards in response to OS
     specific events.
+
+    NOTE: this metric was expired from 2025-03-01 to 2025-11-24.
   </summary>
   <token key="PointerDigitizerType">
     <variant name="DirectPen" summary="pen digitizer integrated into display"/>
@@ -46,9 +49,10 @@
 </histogram>
 
 <histogram name="Input.Digitizer.MaxTouchPointsSupportedBySystemAtStartup"
-    units="maximum supported contacts" expires_after="2025-03-01">
+    units="maximum supported contacts" expires_after="2027-01-01">
   <owner>kouhei@chromium.org</owner>
-  <owner>Adam.Ettenberger@microsoft.com</owner>
+  <owner>gastonr@microsoft.com</owner>
+  <owner>gerchiko@microsoft.com</owner>
   <owner>input-dev@chromium.org</owner>
   <summary>
     Record the maximum number of simultaneous contacts supported by the pointer
@@ -60,13 +64,16 @@
     Currently this metric is only available on Windows. This is logged only once
     per browser session, when initializing the singleton `TouchUiController` at
     startup.
+
+    NOTE: this metric was expired from 2025-03-01 to 2025-11-24.
   </summary>
 </histogram>
 
 <histogram name="Input.Digitizer.{Interaction}" enum="PointerDigitizerType"
-    expires_after="2025-03-01">
+    expires_after="2027-01-01">
   <owner>kouhei@chromium.org</owner>
-  <owner>Adam.Ettenberger@microsoft.com</owner>
+  <owner>gastonr@microsoft.com</owner>
+  <owner>gerchiko@microsoft.com</owner>
   <owner>input-dev@chromium.org</owner>
   <summary>
     Record each pointer digitizer found at startup, and each pointer digitizer
@@ -81,6 +88,8 @@
 
     OnStartup : Logged only once per browser session for each pointer digitizer
     discovered when initializing the singleton `TouchUiController` at startup.
+
+    NOTE: this metric was expired from 2025-03-01 to 2025-11-24.
   </summary>
   <token key="Interaction">
     <variant name="OnConnected" summary="connected following startup"/>
diff --git a/tools/metrics/histograms/metadata/magic_stack/enums.xml b/tools/metrics/histograms/metadata/magic_stack/enums.xml
index faf129a..dddb2c0 100644
--- a/tools/metrics/histograms/metadata/magic_stack/enums.xml
+++ b/tools/metrics/histograms/metadata/magic_stack/enums.xml
@@ -46,6 +46,7 @@
   <int value="8" label="Tab Group Sync Promo Card"/>
   <int value="9" label="Quick Delete Promo Card"/>
   <int value="10" label="History Sync Promo Card"/>
+  <int value="11" label="Tips Notifications Promo Card"/>
 </enum>
 
 </enums>
diff --git a/tools/metrics/histograms/metadata/magic_stack/histograms.xml b/tools/metrics/histograms/metadata/magic_stack/histograms.xml
index 98bfef4..d7cbbbf7 100644
--- a/tools/metrics/histograms/metadata/magic_stack/histograms.xml
+++ b/tools/metrics/histograms/metadata/magic_stack/histograms.xml
@@ -38,6 +38,7 @@
   <variant name="TabGroupPromo"/>
   <variant name="TabGroupSyncPromo"/>
   <variant name="TabResumption"/>
+  <variant name="TipsNotificationsPromo"/>
 </variants>
 
 <variants name="ScrollState">
diff --git a/tools/metrics/histograms/metadata/media/enums.xml b/tools/metrics/histograms/metadata/media/enums.xml
index 5e6a6c06..c1c696763 100644
--- a/tools/metrics/histograms/metadata/media/enums.xml
+++ b/tools/metrics/histograms/metadata/media/enums.xml
@@ -26,6 +26,29 @@
 
 <enums>
 
+<enum name="ActiveGpuInfo">
+  <int value="1" label="Intel"/>
+  <int value="2" label="NVIDIA"/>
+  <int value="4" label="AMD"/>
+  <int value="8" label="Other"/>
+  <int value="17" label="Intel | Intel (Non-active)"/>
+  <int value="18" label="NVIDIA | Intel (Non-active)"/>
+  <int value="20" label="AMD | Intel (Non-active)"/>
+  <int value="24" label="Other | Intel (Non-active)"/>
+  <int value="33" label="Intel | NVIDIA (Non-active)"/>
+  <int value="34" label="NVIDIA | NVIDIA (Non-active)"/>
+  <int value="36" label="AMD | NVIDIA (Non-active)"/>
+  <int value="40" label="Other | NVIDIA (Non-active)"/>
+  <int value="65" label="Intel | AMD (Non-active)"/>
+  <int value="66" label="NVIDIA | AMD (Non-active)"/>
+  <int value="68" label="AMD | AMD (Non-active)"/>
+  <int value="72" label="Other | AMD (Non-active)"/>
+  <int value="129" label="Intel | Other (Non-active)"/>
+  <int value="130" label="NVIDIA | Other (Non-active)"/>
+  <int value="132" label="AMD | Other (Non-active)"/>
+  <int value="136" label="Other | Other (Non-active)"/>
+</enum>
+
 <enum name="AdaptationReason">
   <int value="0" label="kUserSelection"/>
   <int value="1" label="kResolutionChange"/>
@@ -1234,6 +1257,12 @@
   <int value="4" label="ChromeOS quick settings mini player (Cast button)"/>
 </enum>
 
+<enum name="GpuOrDisplayCount">
+  <int value="0" label="Unknown"/>
+  <int value="1" label="One"/>
+  <int value="2" label="Two or more"/>
+</enum>
+
 <enum name="HighlightedTabDiscardStatus">
   <int value="0" label="NoTabsHighlighted"/>
   <int value="1" label="AllHighlightedTabsNonDiscarded"/>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index b61eb96..21760cd 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -3597,6 +3597,17 @@
   </summary>
 </histogram>
 
+<histogram name="Media.EME.MediaDrm.FirstApiLevel" enum="AndroidApiLevel"
+    expires_after="2026-03-22">
+  <owner>vpasupathy@chromium.org</owner>
+  <owner>media-dev-uma@chromium.org</owner>
+  <summary>
+    The Android Api level the device was released with, retrieved by obtaining
+    |ro.product.first_api_level| from the OS. We record this once at time of
+    checking the FirstApiLevel during the pre-provisioning flow.
+  </summary>
+</histogram>
+
 <histogram name="Media.EME.MediaDrm.GetOriginIdResult" enum="GetOriginIdResult"
     expires_after="2026-05-24">
   <owner>jrummell@chromium.org</owner>
@@ -3767,6 +3778,40 @@
   </summary>
 </histogram>
 
+<histogram
+    name="Media.EME.MediaFoundationService.HardwareContextReset.ActiveGpuInfo"
+    enum="ActiveGpuInfo" expires_after="2026-04-05">
+  <owner>sangbaekpark@chromium.org</owner>
+  <owner>media-dev-uma@chromium.org</owner>
+  <summary>
+    Active and non-active GPU vendor information when a HardwareContextReset
+    event happened in the MediaFoundationService process. Reported whenever such
+    an event happens.
+  </summary>
+</histogram>
+
+<histogram
+    name="Media.EME.MediaFoundationService.HardwareContextReset.DisplayCount"
+    enum="GpuOrDisplayCount" expires_after="2026-04-05">
+  <owner>sangbaekpark@chromium.org</owner>
+  <owner>media-dev-uma@chromium.org</owner>
+  <summary>
+    The number of displays when a HardwareContextReset event happened in the
+    MediaFoundationService process. Reported whenever such an event happens.
+  </summary>
+</histogram>
+
+<histogram
+    name="Media.EME.MediaFoundationService.HardwareContextReset.GpuCount"
+    enum="GpuOrDisplayCount" expires_after="2026-04-05">
+  <owner>sangbaekpark@chromium.org</owner>
+  <owner>media-dev-uma@chromium.org</owner>
+  <summary>
+    The number of GPUs when a HardwareContextReset event happened in the
+    MediaFoundationService process. Reported whenever such an event happens.
+  </summary>
+</histogram>
+
 <histogram name="Media.EME.MediaFoundationService.IsKeySystemSupported"
     units="ms" expires_after="2026-05-24">
   <owner>xhwang@chromium.org</owner>
@@ -5096,6 +5141,37 @@
   </summary>
 </histogram>
 
+<histogram name="Media.MediaFoundationRenderer.PlaybackError.ActiveGpuInfo"
+    enum="ActiveGpuInfo" expires_after="2026-04-05">
+  <owner>sangbaekpark@chromium.org</owner>
+  <owner>media-dev-uma@chromium.org</owner>
+  <summary>
+    Active and non-active GPU vendor information when the
+    MediaFoundationRenderer hits a playback error. Reported whenever such an
+    event happens.
+  </summary>
+</histogram>
+
+<histogram name="Media.MediaFoundationRenderer.PlaybackError.DisplayCount"
+    enum="GpuOrDisplayCount" expires_after="2026-04-05">
+  <owner>sangbaekpark@chromium.org</owner>
+  <owner>media-dev-uma@chromium.org</owner>
+  <summary>
+    The number of displays when the MediaFoundationRenderer hits a playback
+    error. Reported whenever such an event happens.
+  </summary>
+</histogram>
+
+<histogram name="Media.MediaFoundationRenderer.PlaybackError.GpuCount"
+    enum="GpuOrDisplayCount" expires_after="2026-04-05">
+  <owner>sangbaekpark@chromium.org</owner>
+  <owner>media-dev-uma@chromium.org</owner>
+  <summary>
+    The number of GPUs when the MediaFoundationRenderer hits a playback error.
+    Reported whenever such an event happens.
+  </summary>
+</histogram>
+
 <histogram
     name="Media.MediaFoundationRenderer.RenderedVideoFrameDetectionResult"
     enum="MediaFoundationRendererRenderedVideoFrameDetectionResult"
diff --git a/tools/metrics/histograms/metadata/navigation/enums.xml b/tools/metrics/histograms/metadata/navigation/enums.xml
index ecab7a32..1e1dc44 100644
--- a/tools/metrics/histograms/metadata/navigation/enums.xml
+++ b/tools/metrics/histograms/metadata/navigation/enums.xml
@@ -26,38 +26,6 @@
 
 <enums>
 
-<enum name="AnimationAbortReason">
-  <int value="0" label="Render Widget Host Destroyed"/>
-  <int value="1" label="Main Commit On Subframe Transition"/>
-  <int value="2" label="[DEPRECATED] New Commit In Primary Main Frame"/>
-  <int value="3" label="[DEPRECATED] Cross Origin Redirect"/>
-  <int value="4"
-      label="[DEPRECATED] New Commit While Displaying Invoke Animation"/>
-  <int value="5"
-      label="[DEPRECATED] New Commit While Displaying Canceled Animation"/>
-  <int value="6"
-      label="[DEPRECATED] New Commit While Waiting For New Renderer To Draw"/>
-  <int value="7"
-      label="[DEPRECATED] New Commit While Waiting For Content For Navigation
-             Entry Shown"/>
-  <int value="8"
-      label="[DEPRECATED] New Commit While Displaying Cross Fade Animation"/>
-  <int value="9"
-      label="[DEPRECATED] New Commit While Waiting For Before Unload Response"/>
-  <int value="10" label="Multiple Navigation Requests Created"/>
-  <int value="11" label="Navigation Entry Deleted Before Commit"/>
-  <int value="12" label="[DEPRECATED] Post Navigation First Frame Timeout"/>
-  <int value="13" label="Chained Back"/>
-  <int value="14" label="Detached From Window"/>
-  <int value="15" label="Root Window Visibility Changed"/>
-  <int value="16" label="Compositor Detached"/>
-  <int value="17" label="Animation Manager Destroyed"/>
-  <int value="18" label="Physical size changed mid-animation"/>
-  <int value="19" label="Animation successfully finishes"/>
-  <int value="20" label="Primary Main Frame Render Process Is Destroyed"/>
-  <int value="21" label="Same-doc navigation restarts as cross-doc"/>
-</enum>
-
 <enum name="AutomaticBeaconOutcome">
   <int value="0" label="Success"/>
   <int value="1" label="No transient user activation"/>
diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml
index 6ef4786..a09c169 100644
--- a/tools/metrics/histograms/metadata/navigation/histograms.xml
+++ b/tools/metrics/histograms/metadata/navigation/histograms.xml
@@ -1188,17 +1188,6 @@
   </summary>
 </histogram>
 
-<histogram name="Navigation.GestureTransition.AnimationAbortReason"
-    enum="AnimationAbortReason" expires_after="2025-12-07">
-  <owner>liuwilliam@google.com</owner>
-  <owner>baranerf@google.com</owner>
-  <owner>chrome-seamless-core@google.com</owner>
-  <summary>
-    Counts various reasons causing the gesture transition animation to be
-    aborted. This metric is recorded once per gesture.
-  </summary>
-</histogram>
-
 <histogram name="Navigation.GestureTransition.CacheHitOrMissReason"
     enum="NavigationTransitionCacheHitOrMissReason" expires_after="2026-04-12">
   <owner>liuwilliam@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/new_tab_page/enums.xml b/tools/metrics/histograms/metadata/new_tab_page/enums.xml
index 1d5fe06..9b7d9ae4 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/enums.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/enums.xml
@@ -524,6 +524,19 @@
   <int value="1" label="Success"/>
 </enum>
 
+<enum name="NtpThemeColorId">
+  <int value="0" label="Default"/>
+  <int value="1" label="NTP Colors Blue"/>
+  <int value="2" label="NTP Colors Aqua"/>
+  <int value="3" label="NTP Colors Green"/>
+  <int value="4" label="NTP Colors Viridian"/>
+  <int value="5" label="NTP Colors Citron"/>
+  <int value="6" label="NTP Colors Orange"/>
+  <int value="7" label="NTP Colors Rose"/>
+  <int value="8" label="NTP Colors Fuchsia"/>
+  <int value="9" label="NTP Colors Violet"/>
+</enum>
+
 <enum name="NTPTileTitleSource">
   <summary>
     The source where the displayed title of an NTP tile originates from.
diff --git a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
index 07966f5..fe940c7 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
@@ -471,6 +471,18 @@
   </summary>
 </histogram>
 
+<histogram name="NewTabPage.Customization.Theme.ChromeColor.ColorId"
+    enum="NtpThemeColorId" expires_after="2026-05-24">
+  <owner>hanxi@chromium.org</owner>
+  <owner>yyanting@google.com</owner>
+  <owner>clank-start@google.com</owner>
+  <summary>
+    Records the last color ID of the Chrome theme colors that was selected by
+    the user from the Chrome Colors bottom sheet. This metric is logged when the
+    bottom sheet is closed. This metric is logged on Android only.
+  </summary>
+</histogram>
+
 <histogram
     name="NewTabPage.Customization.Theme.ThemeCollection.CollectionSelected"
     enum="NTPCollectionId" expires_after="2026-05-17">
diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml
index d9de2d2..f6df511c 100644
--- a/tools/metrics/histograms/metadata/omnibox/histograms.xml
+++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml
@@ -1029,6 +1029,7 @@
     expires_after="2026-09-14">
   <owner>ender@google.com</owner>
   <owner>woa-performance-bugs+jank@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Measures the amount of time it takes to call UrlBar.calculateVisibleHint().
     Recorded once per call.
@@ -1041,6 +1042,7 @@
   <owner>jdonnelly@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
   <owner>chrome-desktop-search@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Records the time taken between a keystroke being typed in the omnibox and
     the text being painted. If there are multiple keystrokes before a paint,
@@ -1081,6 +1083,7 @@
   <owner>mpearson@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
   <owner>chrome-desktop-search@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     A refinement of Omnibox.CharTypedToRepaintLatency metric. It measures the
     time between the first character insertion in a series that happen during a
@@ -1106,6 +1109,7 @@
   <owner>mpearson@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
   <owner>chrome-desktop-search@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Records the time between when OnPaint() is called to the time the compositor
     reports pixels were successfully drawn to the screen. This a subset of the
@@ -1130,6 +1134,7 @@
   <owner>mpearson@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
   <owner>chrome-desktop-search@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Records the time taken between a keystroke being typed in the omnibox and
     the time when we're ready to paint the omnibox. This is a breakdown
@@ -1379,6 +1384,7 @@
     expires_after="2026-05-24">
   <owner>manukh@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Measures the duration between when the async backend request was sent and
     response received.
@@ -1445,6 +1451,7 @@
     expires_after="2026-05-24">
   <owner>manukh@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Measures the duration between when the provider begins (after a debouncing
     delay) and ends.
@@ -1914,6 +1921,7 @@
     expires_after="2026-11-12">
   <owner>orinj@chromium.org</owner>
   <owner>jdonnelly-team@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     The time taken by HistoryFuzzyProvider to search for fuzzy input
     corrections. This is measured if and only if a search is performed, so many
@@ -1935,6 +1943,7 @@
     expires_after="2026-11-12">
   <owner>kouhei@google.com</owner>
   <owner>chrome-loading@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Records the time taken between the OS captured input event timestamp to an
     URL is about to be selected. Recorded once per call to
@@ -1951,6 +1960,7 @@
     expires_after="2026-11-12">
   <owner>kouhei@google.com</owner>
   <owner>chrome-loading@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Records the time taken between the OS captured input event timestamp to
     OmniboxAction is about to be executed. Recorded once per call to
@@ -1967,6 +1977,7 @@
     expires_after="2026-11-12">
   <owner>kouhei@google.com</owner>
   <owner>chrome-loading@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Records the time taken between the OS captured input event timestamp to
     OmniboxEditModel::OpenSelection. Recorded once per call to
@@ -2416,6 +2427,7 @@
     units="ms" expires_after="2026-11-12">
   <owner>niharm@google.com</owner>
   <owner>chrome-desktop-search@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     The amount of time for QueryMostVisitedURLs() to invoke its callback in the
     Most Visited Sites Provider. Only recorded when the history service callback
@@ -2777,6 +2789,7 @@
     units="ms" expires_after="2026-11-12">
   <owner>mahmadi@chromium.org</owner>
   <owner>chrome-desktop-search@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Records the time between the WebUI page load which happens when the omnibox
     popup presenter is constructed and the first time the popup widget is shown.
@@ -2802,6 +2815,7 @@
     units="ms" expires_after="2026-11-12">
   <owner>mahmadi@chromium.org</owner>
   <owner>chrome-desktop-search@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Records the time between AutocompleteController::Observer's OnResultChanged
     is called and the time when the &lt;cr-searchbox-dropdown&gt; is ready to
@@ -2943,6 +2957,7 @@
     expires_after="2026-03-09">
   <owner>ender@google.com</owner>
   <owner>woa-performance-bugs+jank@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Measures the amount of time it takes to call UrlBar.scrollToTLD(). Logged
     once per call.
@@ -4242,6 +4257,7 @@
     expires_after="2026-11-12">
   <owner>mahmadi@chromium.org</owner>
   <owner>chrome-desktop-search@google.com</owner>
+  <improvement direction="LOWER_IS_BETTER"/>
   <summary>
     Records the time between a keystroke being typed in the &lt;cr-searchbox&gt;
     and the time when the &lt;cr-searchbox-dropdown&gt; is ready to render the
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 697d6db..5320b60 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -5423,17 +5423,6 @@
   </summary>
 </histogram>
 
-<histogram name="MPArch.ChildProcessLaunchActivelyInParallel" units="threads"
-    expires_after="2026-01-04">
-  <owner>ajgo@chromium.org</owner>
-  <owner>src/sandbox/policy/win/OWNERS</owner>
-  <summary>
-    The number of process creation threads that are actively spawning sub
-    processes. Recorded at the start of each process launch. Only recorded on
-    Windows and when parallel process launching is enabled.
-  </summary>
-</histogram>
-
 <histogram name="MPArch.ChildProcessLauncher.{Event}" units="ms"
     expires_after="2026-05-24">
   <owner>ajgo@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/puma/histograms.xml b/tools/metrics/histograms/metadata/puma/histograms.xml
index 00e6c9d..7f7fa7d5 100644
--- a/tools/metrics/histograms/metadata/puma/histograms.xml
+++ b/tools/metrics/histograms/metadata/puma/histograms.xml
@@ -22,60 +22,113 @@
 
 <histograms>
 
-<!-- LINT.IfChange(PUMA.RegionalCapabilities.FunnelStage.Eligibility) -->
-
 <histogram name="PUMA.RegionalCapabilities.FunnelStage.Eligibility"
     enum="SearchEngineChoiceScreenConditions" expires_after="2026-08-19">
   <owner>dgn@chromium.org</owner>
   <owner>ljjlee@chromium.org</owner>
   <owner>chrome-regionalcapabilities@google.com</owner>
   <summary>
-    Records the condition state on profile load for whether the profile is
-    possibly eligible for a search engine choice screen.
+    PUMA.RegionalCapabilities.FunnelStage.Eligibility is a version of histogram
+    RegionalCapabilities.FunnelStage.Eligibility that is reported for PUMA.
   </summary>
 </histogram>
 
-<!-- LINT.ThenChange(tools/metrics/histograms/metadata/regional_capabilities/histograms.xml:RegionalCapabilities.FunnelStage.Eligibility) -->
-
-<!-- LINT.IfChange(PUMA.RegionalCapabilities.FunnelStage.Reported) -->
-
 <histogram name="PUMA.RegionalCapabilities.FunnelStage.Reported"
     enum="RegionalCapabilitiesFunnelStage" expires_after="2026-03-22">
   <owner>dgn@chromium.org</owner>
   <owner>chrome-regionalcapabilities@google.com</owner>
   <summary>
-    Reports on which stage of the regional capabilities funnel a given profile
-    is in. (Internal-only: see go/chrome-taiyaki-metrics-dd for more info)
-
-    Can be recorded by profiles on some key events like profile load or attempt
-    to trigger a choice screen.
+    PUMA.RegionalCapabilities.FunnelStage.Reported is a version of histogram
+    RegionalCapabilities.FunnelStage.Reported that is reported for PUMA.
   </summary>
 </histogram>
 
-<!-- LINT.ThenChange(tools/metrics/histograms/metadata/regional_capabilities/histograms.xml:RegionalCapabilities.FunnelStage.Reported) -->
-
-<!-- LINT.IfChange(PUMA.RegionalCapabilities.FunnelStage.Triggering) -->
-
 <histogram name="PUMA.RegionalCapabilities.FunnelStage.Triggering"
     enum="SearchEngineChoiceScreenConditions" expires_after="2026-08-19">
   <owner>dgn@chromium.org</owner>
   <owner>ljjlee@chromium.org</owner>
   <owner>chrome-regionalcapabilities@google.com</owner>
   <summary>
-    Records the condition state at choice screen triggering attempt, for whether
-    we are determining the profile to be eligible for a search engine choice
-    screen. This is recorded every time the dynamic eligibility state is being
-    checked, which might be as often as every page navigation on desktop, or app
-    warm open on iOS. If a profile was deemed ineligible at profile load time
-    (as recorded by the RegionalCapabilities.FunnelStage.Eligibility histogram),
-    this histogram will not be recorded.
+    PUMA.RegionalCapabilities.FunnelStage.Triggering is a version of histogram
+    RegionalCapabilities.FunnelStage.Triggering that is reported for PUMA.
   </summary>
 </histogram>
 
-<!-- LINT.ThenChange(tools/metrics/histograms/metadata/regional_capabilities/histograms.xml:RegionalCapabilities.FunnelStage.Triggering) -->
+<histogram
+    name="PUMA.RegionalCapabilities.Search.ChoiceScreenDefaultSearchEngineType"
+    enum="OmniboxSearchEngineType" expires_after="2026-08-19">
+  <owner>dgn@chromium.org</owner>
+  <owner>chrome-waffle-eng@google.com</owner>
+  <summary>
+    PUMA.RegionalCapabilities.Search.ChoiceScreenDefaultSearchEngineType is a
+    version of histogram Search.ChoiceScreenDefaultSearchEngineType that is
+    reported for PUMA.
+  </summary>
+</histogram>
+
+<histogram
+    name="PUMA.RegionalCapabilities.Search.ChoiceScreenDefaultSearchEngineType2"
+    enum="OmniboxSearchEngineType" expires_after="2026-08-19">
+  <owner>dgn@chromium.org</owner>
+  <owner>chrome-waffle-eng@google.com</owner>
+  <summary>
+    PUMA.RegionalCapabilities.Search.ChoiceScreenDefaultSearchEngineType2 is a
+    version of histogram Search.ChoiceScreenDefaultSearchEngineType2 that is
+    reported for PUMA.
+  </summary>
+</histogram>
+
+<histogram name="PUMA.RegionalCapabilities.Search.ChoiceScreenEvents"
+    enum="SearchEngineChoiceScreenEvents" expires_after="2026-08-19">
+  <owner>dgn@chromium.org</owner>
+  <owner>samarchehade@chromium.org</owner>
+  <owner>chrome-waffle-eng@google.com</owner>
+  <summary>
+    PUMA.RegionalCapabilities.Search.ChoiceScreenEvents is a version of
+    histogram Search.ChoiceScreenEvents that is reported for PUMA.
+  </summary>
+</histogram>
+
+<histogram
+    name="PUMA.RegionalCapabilities.Search.ChoiceScreenNavigationConditions"
+    enum="SearchEngineChoiceScreenConditions" expires_after="2026-08-19">
+  <owner>dgn@chromium.org</owner>
+  <owner>ljjlee@chromium.org</owner>
+  <owner>chrome-waffle-eng@google.com</owner>
+  <summary>
+    PUMA.RegionalCapabilities.Search.ChoiceScreenNavigationConditions is a
+    version of histogram Search.ChoiceScreenNavigationConditions that is
+    reported for PUMA.
+  </summary>
+</histogram>
+
+<histogram
+    name="PUMA.RegionalCapabilities.Search.ChoiceScreenProfileInitConditions"
+    enum="SearchEngineChoiceScreenConditions" expires_after="2026-08-19">
+  <owner>dgn@chromium.org</owner>
+  <owner>ljjlee@chromium.org</owner>
+  <owner>chrome-waffle-eng@google.com</owner>
+  <summary>
+    PUMA.RegionalCapabilities.Search.ChoiceScreenProfileInitConditions is a
+    version of histogram Search.ChoiceScreenProfileInitConditions that is
+    reported for PUMA.
+  </summary>
+</histogram>
+
+<histogram
+    name="PUMA.RegionalCapabilities.Search.ChoiceScreenSelectedEngineIndex"
+    units="index" expires_after="2026-08-19">
+  <owner>dgn@chromium.org</owner>
+  <owner>chrome-waffle-eng@google.com</owner>
+  <summary>
+    PUMA.RegionalCapabilities.Search.ChoiceScreenSelectedEngineIndex is a
+    version of histogram Search.ChoiceScreenSelectedEngineIndex that is reported
+    for PUMA.
+  </summary>
+</histogram>
 
 <histogram name="PUMA.RegionalCapabilities.Session.TotalDuration.Recorded"
-    units="ms" expires_after="2026-11-21">
+    enum="Boolean" expires_after="2026-11-21">
   <owner>asvitkine@chromium.org</owner>
   <owner>chrome-analysis-team@google.com</owner>
   <owner>chrome-regionalcapabilities@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/startup/histograms.xml b/tools/metrics/histograms/metadata/startup/histograms.xml
index 95f0b89..4e0cd9d 100644
--- a/tools/metrics/histograms/metadata/startup/histograms.xml
+++ b/tools/metrics/histograms/metadata/startup/histograms.xml
@@ -1220,7 +1220,7 @@
 </histogram>
 
 <histogram name="Startup.StartupTabs.IsWelcomePageSkipped"
-    enum="BooleanSkipped" expires_after="2025-11-20">
+    enum="BooleanSkipped" expires_after="2026-05-20">
   <owner>dgn@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/tab/enums.xml b/tools/metrics/histograms/metadata/tab/enums.xml
index a250e023..d99a50b 100644
--- a/tools/metrics/histograms/metadata/tab/enums.xml
+++ b/tools/metrics/histograms/metadata/tab/enums.xml
@@ -55,9 +55,10 @@
   <int value="0" label="Killed"/>
   <int value="1" label="Skipped"/>
   <int value="2" label="Killed without unload handlers"/>
+  <int value="3" label="Killed without unload handlers or worker checks"/>
 </enum>
 
-<!-- LINT.ThenChange(//chrome/browser/resource_coordinator/utils.cc:AttemptFastKillForDiscardResult) -->
+<!-- LINT.ThenChange(//chrome/browser/resource_coordinator/utils.h:AttemptFastKillForDiscardResult) -->
 
 <enum name="BooleanFocusedTab">
   <int value="0" label="Not Focused"/>
diff --git a/tools/metrics/histograms/metadata/variations/enums.xml b/tools/metrics/histograms/metadata/variations/enums.xml
index c189f43..609deab 100644
--- a/tools/metrics/histograms/metadata/variations/enums.xml
+++ b/tools/metrics/histograms/metadata/variations/enums.xml
@@ -332,6 +332,12 @@
 
 <!-- LINT.ThenChange(//components/variations/net/variations_command_line.h:VariationsStateEncryptionStatus) -->
 
+<enum name="VariationsTimeSource">
+  <int value="0" label="Unknown"/>
+  <int value="1" label="Local"/>
+  <int value="2" label="Network"/>
+</enum>
+
 </enums>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/variations/histograms.xml b/tools/metrics/histograms/metadata/variations/histograms.xml
index 7f968c6..d8cfb0f 100644
--- a/tools/metrics/histograms/metadata/variations/histograms.xml
+++ b/tools/metrics/histograms/metadata/variations/histograms.xml
@@ -254,6 +254,15 @@
   </summary>
 </histogram>
 
+<histogram name="Variations.Headers.TimeSource" enum="VariationsTimeSource"
+    expires_after="2026-11-01">
+  <owner>rogerm@google.com</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    The time source used by the variations service to get the current time.
+  </summary>
+</histogram>
+
 <histogram
     name="Variations.Headers.URLValidationResult{VariationsHeadersURLValidationResult}"
     enum="VariationsHeadersURLValidationResult" expires_after="2023-11-01">
diff --git a/tools/metrics/histograms/metadata/webapps/histograms.xml b/tools/metrics/histograms/metadata/webapps/histograms.xml
index adf1c6c..b00b20f6 100644
--- a/tools/metrics/histograms/metadata/webapps/histograms.xml
+++ b/tools/metrics/histograms/metadata/webapps/histograms.xml
@@ -2925,7 +2925,7 @@
 </histogram>
 
 <histogram name="WebApp.WebInstallApi.InstallType" enum="WebInstallApiType"
-    expires_after="2026-01-01">
+    expires_after="2026-06-02">
   <owner>dmurph@chromium.org</owner>
   <owner>pwa-team@google.com</owner>
   <owner>liahiscock@microsoft.com</owner>
@@ -2936,7 +2936,7 @@
 </histogram>
 
 <histogram name="WebApp.WebInstallApi.Result" enum="WebInstallApiResult"
-    expires_after="2026-01-01">
+    expires_after="2026-06-02">
   <owner>dmurph@chromium.org</owner>
   <owner>pwa-team@google.com</owner>
   <owner>liahiscock@microsoft.com</owner>
diff --git a/ui/accessibility/platform/inspect/ax_inspect_utils_auralinux.cc b/ui/accessibility/platform/inspect/ax_inspect_utils_auralinux.cc
index 00c2c5d..c145e03a 100644
--- a/ui/accessibility/platform/inspect/ax_inspect_utils_auralinux.cc
+++ b/ui/accessibility/platform/inspect/ax_inspect_utils_auralinux.cc
@@ -29,14 +29,8 @@
 
 const char* GetNameForPlatformConstant(
     base::span<const PlatformConstantToNameEntry> table,
-    size_t spanification_suspected_redundant_table_size,
     int32_t value) {
-  // TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
-  // redundant in M143.
-  CHECK(spanification_suspected_redundant_table_size == table.size(),
-        base::NotFatalUntil::M143);
-  for (size_t i = 0; i < spanification_suspected_redundant_table_size; ++i) {
-    auto& entry = table[i];
+  for (const auto& entry : table) {
     if (entry.value == value)
       return entry.name;
   }
@@ -115,7 +109,7 @@
 #endif
   };
 
-  return GetNameForPlatformConstant(state_table, std::size(state_table), state);
+  return GetNameForPlatformConstant(state_table, state);
 }
 
 const char* ATSPIRelationToString(AtspiRelationType relation) {
@@ -145,8 +139,7 @@
       QUOTE(ATSPI_RELATION_LAST_DEFINED),
   };
 
-  return GetNameForPlatformConstant(relation_table, std::size(relation_table),
-                                    relation);
+  return GetNameForPlatformConstant(relation_table, relation);
 }
 
 const char* ATSPIRoleToString(AtspiRole role) {
@@ -287,7 +280,7 @@
 #endif
   };
 
-  return GetNameForPlatformConstant(role_table, std::size(role_table), role);
+  return GetNameForPlatformConstant(role_table, role);
 }
 
 // This is used to ensure a standard set of AtkRole name conversions between
diff --git a/ui/aura/gestures/gesture_recognizer_unittest.cc b/ui/aura/gestures/gesture_recognizer_unittest.cc
index a3c3bf9d0..c37a852 100644
--- a/ui/aura/gestures/gesture_recognizer_unittest.cc
+++ b/ui/aura/gestures/gesture_recognizer_unittest.cc
@@ -3183,38 +3183,38 @@
   // moved for too long. See ui/events/velocity_tracker/velocity_tracker.cc's
   // kAssumePointerStoppedTimeMs.
   for (size_t count = 2; count <= kTouchPoints; ++count) {
-    generator.GestureMultiFingerScroll(count, base::span(points).first(count),
-                                       10, kSteps, 0, -11 * kSteps);
+    generator.GestureMultiFingerScroll(base::span(points).first(count), 10,
+                                       kSteps, 0, -11 * kSteps);
     EXPECT_TRUE(delegate->swipe_up());
     delegate->Reset();
 
-    generator.GestureMultiFingerScroll(count, base::span(points).first(count),
-                                       10, kSteps, 0, 11 * kSteps);
+    generator.GestureMultiFingerScroll(base::span(points).first(count), 10,
+                                       kSteps, 0, 11 * kSteps);
     EXPECT_TRUE(delegate->swipe_down());
     delegate->Reset();
 
-    generator.GestureMultiFingerScroll(count, base::span(points).first(count),
-                                       10, kSteps, -11 * kSteps, 0);
+    generator.GestureMultiFingerScroll(base::span(points).first(count), 10,
+                                       kSteps, -11 * kSteps, 0);
     EXPECT_TRUE(delegate->swipe_left());
     delegate->Reset();
 
-    generator.GestureMultiFingerScroll(count, base::span(points).first(count),
-                                       10, kSteps, 11 * kSteps, 0);
+    generator.GestureMultiFingerScroll(base::span(points).first(count), 10,
+                                       kSteps, 11 * kSteps, 0);
     EXPECT_TRUE(delegate->swipe_right());
     delegate->Reset();
 
-    generator.GestureMultiFingerScroll(count, base::span(points).first(count),
-                                       10, kSteps, 5 * kSteps, 12 * kSteps);
+    generator.GestureMultiFingerScroll(base::span(points).first(count), 10,
+                                       kSteps, 5 * kSteps, 12 * kSteps);
     EXPECT_FALSE(delegate->swipe_down());
     delegate->Reset();
 
-    generator.GestureMultiFingerScroll(count, base::span(points).first(count),
-                                       10, kSteps, 4 * kSteps, 12 * kSteps);
+    generator.GestureMultiFingerScroll(base::span(points).first(count), 10,
+                                       kSteps, 4 * kSteps, 12 * kSteps);
     EXPECT_TRUE(delegate->swipe_down());
     delegate->Reset();
 
-    generator.GestureMultiFingerScroll(count, base::span(points).first(count),
-                                       10, kSteps, 3 * kSteps, 12 * kSteps);
+    generator.GestureMultiFingerScroll(base::span(points).first(count), 10,
+                                       kSteps, 3 * kSteps, 12 * kSteps);
     EXPECT_TRUE(delegate->swipe_down());
     delegate->Reset();
   }
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev.cc b/ui/events/ozone/evdev/touch_event_converter_evdev.cc
index 4afc658..fbab765 100644
--- a/ui/events/ozone/evdev/touch_event_converter_evdev.cc
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev.cc
@@ -213,8 +213,6 @@
           CreatePalmDetectionFilter(devinfo, shared_palm_state)),
       heatmap_palm_detection_filter_(
           CreateHeatmapPalmDetectionFilter(devinfo, shared_palm_state)),
-      palm_on_touch_major_max_(true),
-      palm_on_tool_type_palm_(true),
       shared_palm_state_(shared_palm_state) {
   if (base::FeatureList::IsEnabled(kEnableNeuralPalmDetectionFilter) &&
       NeuralStylusPalmDetectionFilter::
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev.h b/ui/events/ozone/evdev/touch_event_converter_evdev.h
index 9eda45fe..5b1230de 100644
--- a/ui/events/ozone/evdev/touch_event_converter_evdev.h
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev.h
@@ -9,22 +9,17 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include <bitset>
 #include <memory>
 #include <ostream>
 #include <queue>
 #include <set>
-// See if we compile against new enough headers and add missing definition
-// if the headers are too old.
-#include "base/memory/raw_ptr.h"
-
-#ifndef MT_TOOL_PALM
-#define MT_TOOL_PALM 2
-#endif
+#include <utility>
+#include <vector>
 
 #include "base/component_export.h"
 #include "base/files/file_path.h"
 #include "base/files/scoped_file.h"
+#include "base/memory/raw_ptr.h"
 #include "base/message_loop/message_pump_epoll.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/time/time.h"
@@ -301,13 +296,13 @@
   GetLatestStylusStateCallback get_latest_stylus_state_callback_;
 
   // Do we mark a touch as palm when touch_major is the max?
-  bool palm_on_touch_major_max_;
+  bool palm_on_touch_major_max_ = true;
 
   // Do we mark a touch as palm when the tool type is marked as TOOL_TYPE_PALM ?
-  bool palm_on_tool_type_palm_;
+  bool palm_on_tool_type_palm_ = true;
 
   // The start time of a touch session.
-  std::optional<base::TimeTicks> session_start_time_ = std::nullopt;
+  std::optional<base::TimeTicks> session_start_time_;
 
   // Whether the last touch was detected as palm.
   bool last_touch_is_palm_ = false;
diff --git a/ui/events/test/event_generator.cc b/ui/events/test/event_generator.cc
index e137464..cc221591 100644
--- a/ui/events/test/event_generator.cc
+++ b/ui/events/test/event_generator.cc
@@ -503,25 +503,19 @@
 }
 
 void EventGenerator::GestureMultiFingerScrollWithDelays(
-    int spanification_suspected_redundant_count,
     base::span<const gfx::Point> start,
     base::span<const gfx::Vector2d> delta,
     base::span<const int> delay_adding_finger_ms,
     base::span<const int> delay_releasing_finger_ms,
     int event_separation_time_ms,
     int steps) {
-  // TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
-  // redundant in M143.
-  CHECK(
-      spanification_suspected_redundant_count == static_cast<int>(start.size()),
-      base::NotFatalUntil::M143);
-  const int kMaxTouchPoints = 10;
-  CHECK_LE(spanification_suspected_redundant_count, kMaxTouchPoints);
+  const size_t kMaxTouchPoints = 10;
+  CHECK_LE(start.size(), kMaxTouchPoints);
   CHECK_GT(steps, 0);
 
   std::array<gfx::Point, kMaxTouchPoints> points;
   std::array<gfx::Vector2d, kMaxTouchPoints> delta_per_step;
-  for (int i = 0; i < spanification_suspected_redundant_count; ++i) {
+  for (size_t i = 0; i < start.size(); ++i) {
     points[i] = start[i];
     delta_per_step[i].set_x(delta[i].x() / steps);
     delta_per_step[i].set_y(delta[i].y() / steps);
@@ -531,7 +525,7 @@
   std::array<base::TimeTicks, kMaxTouchPoints> press_time;
   std::array<base::TimeTicks, kMaxTouchPoints> release_time;
   std::array<bool, kMaxTouchPoints> pressed;
-  for (int i = 0; i < spanification_suspected_redundant_count; ++i) {
+  for (size_t i = 0; i < start.size(); ++i) {
     pressed[i] = false;
     press_time[i] =
         press_time_first + base::Milliseconds(delay_adding_finger_ms[i]);
@@ -544,7 +538,7 @@
     base::TimeTicks move_time =
         press_time_first + base::Milliseconds(event_separation_time_ms * step);
 
-    for (int i = 0; i < spanification_suspected_redundant_count; ++i) {
+    for (size_t i = 0; i < start.size(); ++i) {
       if (!pressed[i] && move_time >= press_time[i]) {
         ui::TouchEvent press(
             ui::EventType::kTouchPressed, points[i], press_time[i],
@@ -556,9 +550,7 @@
 
     // All touch release events should occur at the end if
     // |event_separation_time_ms| is 0.
-    for (int i = 0; i < spanification_suspected_redundant_count &&
-                    event_separation_time_ms > 0;
-         ++i) {
+    for (size_t i = 0; i < start.size() && event_separation_time_ms > 0; ++i) {
       if (pressed[i] && move_time >= release_time[i]) {
         ui::TouchEvent release(
             ui::EventType::kTouchReleased, points[i], release_time[i],
@@ -568,7 +560,7 @@
       }
     }
 
-    for (int i = 0; i < spanification_suspected_redundant_count; ++i) {
+    for (size_t i = 0; i < start.size(); ++i) {
       points[i] += delta_per_step[i];
       if (pressed[i]) {
         ui::TouchEvent move(
@@ -582,7 +574,7 @@
   base::TimeTicks default_release_time =
       press_time_first + base::Milliseconds(event_separation_time_ms * steps);
   // Ensures that all pressed fingers are released in the end.
-  for (int i = 0; i < spanification_suspected_redundant_count; ++i) {
+  for (size_t i = 0; i < start.size(); ++i) {
     if (pressed[i]) {
       ui::TouchEvent release(
           ui::EventType::kTouchReleased, points[i], default_release_time,
@@ -594,18 +586,12 @@
 }
 
 void EventGenerator::GestureMultiFingerScrollWithDelays(
-    int spanification_suspected_redundant_count,
     base::span<const gfx::Point> start,
     base::span<const int> delay_adding_finger_ms,
     int event_separation_time_ms,
     int steps,
     int move_x,
     int move_y) {
-  // TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
-  // redundant in M143.
-  CHECK(
-      spanification_suspected_redundant_count == static_cast<int>(start.size()),
-      base::NotFatalUntil::M143);
   const int kMaxTouchPoints = 10;
   std::array<int, kMaxTouchPoints> delay_releasing_finger_ms;
   std::array<gfx::Vector2d, kMaxTouchPoints> delta;
@@ -614,28 +600,20 @@
     delta[i].set_x(move_x);
     delta[i].set_y(move_y);
   }
-  GestureMultiFingerScrollWithDelays(spanification_suspected_redundant_count,
-                                     start, delta, delay_adding_finger_ms,
+  GestureMultiFingerScrollWithDelays(start, delta, delay_adding_finger_ms,
                                      delay_releasing_finger_ms,
                                      event_separation_time_ms, steps);
 }
 
 void EventGenerator::GestureMultiFingerScroll(
-    int spanification_suspected_redundant_count,
     base::span<const gfx::Point> start,
     int event_separation_time_ms,
     int steps,
     int move_x,
     int move_y) {
-  // TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
-  // redundant in M143.
-  CHECK(
-      spanification_suspected_redundant_count == static_cast<int>(start.size()),
-      base::NotFatalUntil::M143);
   const int kMaxTouchPoints = 10;
   int delays[kMaxTouchPoints] = {};
-  GestureMultiFingerScrollWithDelays(spanification_suspected_redundant_count,
-                                     start, delays, event_separation_time_ms,
+  GestureMultiFingerScrollWithDelays(start, delays, event_separation_time_ms,
                                      steps, move_x, move_y);
 }
 
diff --git a/ui/events/test/event_generator.h b/ui/events/test/event_generator.h
index 3e904d9..04be0801 100644
--- a/ui/events/test/event_generator.h
+++ b/ui/events/test/event_generator.h
@@ -425,7 +425,6 @@
   // |event_separation_time_ms| are relevant when testing velocity/fling/swipe,
   // otherwise these can be any non-zero value.
   void GestureMultiFingerScrollWithDelays(
-      int spanification_suspected_redundant_count,
       base::span<const gfx::Point> start,
       base::span<const gfx::Vector2d> delta,
       base::span<const int> delay_adding_finger_ms,
@@ -438,7 +437,6 @@
   // events. All fingers are released at the end of scrolling together. All
   // fingers move the same amount specified by |move_x| and |move_y|.
   void GestureMultiFingerScrollWithDelays(
-      int spanification_suspected_redundant_count,
       base::span<const gfx::Point> start,
       base::span<const int> delay_adding_finger_ms,
       int event_separation_time_ms,
@@ -451,8 +449,7 @@
   // All fingers are pressed at the beginning together and are released at the
   // end of scrolling together. All fingers move move the same amount specified
   // by |move_x| and |move_y|.
-  void GestureMultiFingerScroll(int spanification_suspected_redundant_count,
-                                base::span<const gfx::Point> start,
+  void GestureMultiFingerScroll(base::span<const gfx::Point> start,
                                 int event_separation_time_ms,
                                 int steps,
                                 int move_x,
diff --git a/ui/gfx/geometry/transform_unittest.cc b/ui/gfx/geometry/transform_unittest.cc
index 9229991..a5b5c4e 100644
--- a/ui/gfx/geometry/transform_unittest.cc
+++ b/ui/gfx/geometry/transform_unittest.cc
@@ -3942,16 +3942,11 @@
       return is_valid_point(r.origin()) && std::isfinite(r.width()) &&
              std::isfinite(r.height());
     };
-    auto is_valid_array =
-        [&](base::span<const float> a,
-            size_t spanification_suspected_redundant_size) -> bool {
-      // TODO(crbug.com/431824301): Remove unneeded parameter once validated to
-      // be redundant in M143.
-      CHECK(spanification_suspected_redundant_size == a.size(),
-            base::NotFatalUntil::M143);
-      for (size_t i = 0; i < spanification_suspected_redundant_size; i++) {
-        if (!std::isfinite(a[i]))
+    auto is_valid_array = [&](base::span<const float> a) -> bool {
+      for (const float& val : a) {
+        if (!std::isfinite(val)) {
           return false;
+        }
       }
       return true;
     };
@@ -3973,7 +3968,7 @@
 
       float v4[4] = {factor, factor, factor, factor};
       m.TransformVector4(v4);
-      EXPECT_TRUE(is_valid_array(v4, 4));
+      EXPECT_TRUE(is_valid_array(v4));
 
       auto v2 = m.To2dTranslation();
       EXPECT_TRUE(is_valid_vector2(v2)) << v2.ToString();
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index becf47a..987d7cc 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -1170,7 +1170,6 @@
       "test/event_generator_delegate_mac.mm",
       "test/scoped_views_test_helper_cocoa.mm",
       "test/test_views_delegate_mac.mm",
-      "test/views_test_base_mac.mm",
       "test/views_test_helper_mac.h",
       "test/views_test_helper_mac.mm",
       "test/widget_test_mac.mm",
diff --git a/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc b/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc
index d0b5342..15d3a615 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc
+++ b/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc
@@ -887,17 +887,14 @@
 struct ArrowTestParameters {
   views::BubbleBorder::Arrow arrow;
   bool adjust_if_offscreen;
-  gfx::Rect anchor_rect;
+  gfx::Rect anchor_rect_in_window;
   views::BubbleBorder::Arrow expected_arrow;
 
-  gfx::Size ExpectedSpace() const {
-    gfx::Rect adjusted_anchor_rect = anchor_rect;
-    adjusted_anchor_rect.Offset(
-        0, ViewsTestBase::GetSystemReservedHeightAtTopOfScreen());
+  gfx::Size ExpectedSpace(gfx::Rect anchor_rect_in_screen) const {
     gfx::Rect screen_rect = gfx::Rect(0, 0, kScreenWidth, kScreenHeight);
 
     return BubbleDialogDelegate::GetAvailableSpaceToPlaceBubble(
-        expected_arrow, adjusted_anchor_rect, screen_rect);
+        expected_arrow, anchor_rect_in_screen, screen_rect);
   }
 };
 
@@ -955,14 +952,18 @@
   bubble_delegate->SetArrow(kParam.arrow);
   bubble_delegate->set_adjust_if_offscreen(kParam.adjust_if_offscreen);
   anchor_widget->GetContentsView()->SetBounds(
-      kParam.anchor_rect.x(), kParam.anchor_rect.y(),
-      kParam.anchor_rect.width(), kParam.anchor_rect.height());
+      kParam.anchor_rect_in_window.x(),
+      kParam.anchor_rect_in_window.y(),
+      kParam.anchor_rect_in_window.width(),
+      kParam.anchor_rect_in_window.height());
+  gfx::Rect anchor_rect_in_screen =
+      bubble_delegate->GetAnchorView()->GetBoundsInScreen();
   gfx::Size available_space =
       BubbleDialogDelegate::GetMaxAvailableScreenSpaceToPlaceBubble(
           bubble_delegate->GetAnchorView(), bubble_delegate->arrow(),
           bubble_delegate->adjust_if_offscreen(),
           BubbleFrameView::PreferredArrowAdjustment::kMirror);
-  EXPECT_EQ(available_space, kParam.ExpectedSpace());
+  EXPECT_EQ(available_space, kParam.ExpectedSpace(anchor_rect_in_screen));
 
   // Repeat via TrackedElement.
   ui::TrackedElement* as_tracked_element =
@@ -974,7 +975,7 @@
           as_tracked_element, bubble_delegate->arrow(),
           bubble_delegate->adjust_if_offscreen(),
           BubbleFrameView::PreferredArrowAdjustment::kMirror);
-  EXPECT_EQ(available_space, kParam.ExpectedSpace());
+  EXPECT_EQ(available_space, kParam.ExpectedSpace(anchor_rect_in_screen));
 }
 
 const int kAnchorFarRightX = 840;
diff --git a/ui/views/bubble/bubble_dialog_model_host_unittest.cc b/ui/views/bubble/bubble_dialog_model_host_unittest.cc
index 2c2f7b55..c9852c6 100644
--- a/ui/views/bubble/bubble_dialog_model_host_unittest.cc
+++ b/ui/views/bubble/bubble_dialog_model_host_unittest.cc
@@ -184,12 +184,13 @@
 }
 
 TEST_F(BubbleDialogModelHostTest, OverrideDefaultButtonDeathTest) {
-  EXPECT_CHECK_DEATH(std::make_unique<BubbleDialogModelHost>(
-      ui::DialogModel::Builder()
-          .AddCancelButton(base::DoNothing())
-          .OverrideDefaultButton(ui::mojom::DialogButton::kOk)
-          .Build(),
-      /*anchor_view=*/nullptr, BubbleBorder::Arrow::TOP_RIGHT))
+  EXPECT_CHECK_DEATH(
+      std::ignore = std::make_unique<BubbleDialogModelHost>(
+          ui::DialogModel::Builder()
+              .AddCancelButton(base::DoNothing())
+              .OverrideDefaultButton(ui::mojom::DialogButton::kOk)
+              .Build(),
+          /*anchor_view=*/nullptr, BubbleBorder::Arrow::TOP_RIGHT))
       << "Cannot override the default button with a button which does not "
          "exist.";
 }
diff --git a/ui/views/controls/slider_unittest.cc b/ui/views/controls/slider_unittest.cc
index 4fd2601e..778d7e7 100644
--- a/ui/views/controls/slider_unittest.cc
+++ b/ui/views/controls/slider_unittest.cc
@@ -472,8 +472,8 @@
   gfx::Point points[] = {gfx::Point(0, 0.1 * max_y()),
                          gfx::Point(0, 0.2 * max_y())};
   event_generator()->GestureMultiFingerScroll(
-      2 /* count */, points, 0 /* event_separation_time_ms */, 5 /* steps */,
-      2 /* move_x */, 0 /* move_y */);
+      points, 0 /* event_separation_time_ms */, 5 /* steps */, 2 /* move_x */,
+      0 /* move_y */);
 
   EXPECT_EQ(1, slider_listener.last_drag_started_epoch());
   EXPECT_GT(slider_listener.last_drag_ended_epoch(),
diff --git a/ui/views/controls/textfield/textfield_unittest.cc b/ui/views/controls/textfield/textfield_unittest.cc
index 7fcfbec..5643e345 100644
--- a/ui/views/controls/textfield/textfield_unittest.cc
+++ b/ui/views/controls/textfield/textfield_unittest.cc
@@ -3901,7 +3901,7 @@
   const gfx::Point kStart2 = kStart1 + gfx::Vector2d(20, 0);
   const gfx::Point kStart[] = {kStart1, kStart2};
   event_generator_->GestureMultiFingerScroll(
-      /*count=*/2, kStart,
+      kStart,
       /*event_separation_time_ms=*/50,
       /*steps=*/5, /*move_x=*/kDisplayOffsetXAdjustment,
       /*move_y=*/0);
@@ -4012,8 +4012,8 @@
   constexpr int kDelayAddingFingerMs[] = {0, 40};
   constexpr int kDelayReleasingFingerMs[] = {150, 150};
   event_generator_->GestureMultiFingerScrollWithDelays(
-      /*count=*/2, kStart, kDelta, kDelayAddingFingerMs,
-      kDelayReleasingFingerMs, /*event_separation_time_ms=*/20, /*steps=*/5);
+      kStart, kDelta, kDelayAddingFingerMs, kDelayReleasingFingerMs,
+      /*event_separation_time_ms=*/20, /*steps=*/5);
 
   // Since the scroll started with one finger, the cursor should have moved.
   gfx::Range range;
diff --git a/ui/views/test/views_test_base.cc b/ui/views/test/views_test_base.cc
index 4be1297..fcfb164 100644
--- a/ui/views/test/views_test_base.cc
+++ b/ui/views/test/views_test_base.cc
@@ -133,12 +133,6 @@
 }
 #endif
 
-#if !BUILDFLAG(IS_MAC)
-int ViewsTestBase::GetSystemReservedHeightAtTopOfScreen() {
-  return 0;
-}
-#endif
-
 gfx::NativeWindow ViewsTestBase::GetContext() {
   return test_helper_->GetContext();
 }
diff --git a/ui/views/test/views_test_base.h b/ui/views/test/views_test_base.h
index 36649418..aa0e0cd 100644
--- a/ui/views/test/views_test_base.h
+++ b/ui/views/test/views_test_base.h
@@ -109,10 +109,6 @@
   void SimulateDesktopNativeDestroy(Widget* widget);
 #endif
 
-  // Get the system reserved height at the top of the screen. On Mac, this
-  // includes the menu bar and title bar.
-  static int GetSystemReservedHeightAtTopOfScreen();
-
  protected:
   base::test::TaskEnvironment* task_environment() {
     return task_environment_.get();
diff --git a/ui/views/test/views_test_base_mac.mm b/ui/views/test/views_test_base_mac.mm
deleted file mode 100644
index 5e85b4a..0000000
--- a/ui/views/test/views_test_base_mac.mm
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/views/test/views_test_base.h"
-
-#include <Cocoa/Cocoa.h>
-
-namespace views {
-
-int ViewsTestBase::GetSystemReservedHeightAtTopOfScreen() {
-  // Includes gap of 1 px b/w menu bar and title bar.
-  CGFloat menu_bar_height = NSHeight(NSScreen.mainScreen.frame) -
-                            NSScreen.mainScreen.visibleFrame.origin.y -
-                            NSHeight(NSScreen.mainScreen.visibleFrame);
-  CGFloat title_bar_height =
-      NSHeight([NSWindow frameRectForContentRect:NSZeroRect
-                                       styleMask:NSWindowStyleMaskTitled]);
-
-  return menu_bar_height + title_bar_height;
-}
-
-}  // namespace views
diff --git a/ui/webui/resources/cr_components/composebox/composebox.ts b/ui/webui/resources/cr_components/composebox/composebox.ts
index 9dc7aef..c6844f3 100644
--- a/ui/webui/resources/cr_components/composebox/composebox.ts
+++ b/ui/webui/resources/cr_components/composebox/composebox.ts
@@ -342,7 +342,7 @@
       } else if (!this.lastQueriedInput_) {
         // This is for cases when focus leaves the matches/input.
         // If there was already text in the input do not clear it.
-        this.input_ = '';
+        this.clearInput();
         this.submitEnabled_ = this.contextFilesSize_ > 0;
       } else {
         // For typed queries reset the input back to typed value when
@@ -1024,23 +1024,25 @@
       return;
     }
 
-    // The first autcomplete response for ZPS contains no matches, since
-    // composebox doesn't support ZPS from local providers (ex. history
-    // suggestion). Similarly, since composebox doesn't support local providers,
-    // typed suggest first response returns a single verbatim match, which
-    // doesn't show in the dropdown. To prevent closing the dropdown before the
-    // actual response from the suggest server is received, ignore the first
-    // response. Only do this if the no flicker fix is enabled. This is guarded
-    // because its not confirmed that the ACController will always return two
-    // responses for a single query.
     // TODO(crbug.com/460888279): This is a temporary, merge safe fix. Ideally,
     // the ACController is not sending multiple responses for a single query,
     // especially when the matches is empty. Remove this logic once a long term
     // fix is found.
     if (this.composeboxNoFlickerSuggestionsFix_ && this.showTypedSuggest_ &&
         !this.haveReceivedAutcompleteResponse_) {
+      // The first autcomplete response for ZPS contains no matches, since
+      // composebox doesn't support ZPS from local providers (ex. history
+      // suggestion). Similarly, since composebox doesn't support local
+      // providers, typed suggest first response returns a single verbatim
+      // match, which doesn't show in the dropdown. To prevent closing the
+      // dropdown before the actual response from the suggest server is
+      // received, add the previous non-verbatim matches to this first response.
+      if (this.result_ && this.result_.matches.length > 0 &&
+          result.matches.length <= 1) {
+        result.matches.push(...this.result_.matches.filter(
+            match => match.type !== 'search-what-you-typed'));
+      }
       this.haveReceivedAutcompleteResponse_ = true;
-      return;
     }
     this.haveReceivedAutcompleteResponse_ = true;
     this.result_ = result;
@@ -1164,7 +1166,7 @@
   }
 
   clearAllInputs() {
-    this.input_ = '';
+    this.clearInput();
     this.$.context.resetContextFiles();
     this.contextFilesSize_ = 0;
     this.smartComposeInlineHint_ = '';
@@ -1172,6 +1174,10 @@
     this.submitEnabled_ = false;
   }
 
+  clearInput() {
+    this.input_ = '';
+  }
+
   getInputText(): string {
     return this.input_;
   }
diff --git a/ui/webui/resources/cr_components/composebox/contextual_entrypoint_and_carousel.ts b/ui/webui/resources/cr_components/composebox/contextual_entrypoint_and_carousel.ts
index 0db2925..bc83726 100644
--- a/ui/webui/resources/cr_components/composebox/contextual_entrypoint_and_carousel.ts
+++ b/ui/webui/resources/cr_components/composebox/contextual_entrypoint_and_carousel.ts
@@ -463,6 +463,14 @@
     });
   }
 
+  private isFileAllowed_(file: File, acceptedFileTypes: string): boolean {
+    const fileType = file.type.toLowerCase();
+    const allowedTypes = acceptedFileTypes.split(',');
+    return allowedTypes.some(type => {
+      return fileType === type;
+    });
+  }
+
   protected processFiles_(files: FileList|null) {
     if (!files || files.length === 0) {
       return;
@@ -482,12 +490,9 @@
         errorToDisplay = Math.max(errorToDisplay, sizeError);
         continue;
       }
-      // TODO(crbug.com/460228091): The current frontend check is broader than
-      // the backend's validation (e.g. allows SVGs). This can lead to a file
-      // reserving a slot here, only to be rejected by the backend later
-      // resulting in fewer files uploaded as expected.
-      // In the future, only reserve slots when the file upload is successful.
-      if (!file.type.includes('pdf') && !file.type.includes('image')) {
+
+      if (!this.isFileAllowed_(file, this.imageFileTypes_) &&
+          !this.isFileAllowed_(file, this.attachmentFileTypes_)) {
         errorToDisplay =
             Math.max(errorToDisplay, ProcessFilesError.INVALID_TYPE);
         continue;
diff --git a/ui/webui/resources/cr_components/composebox/file_thumbnail.css b/ui/webui/resources/cr_components/composebox/file_thumbnail.css
index f0a05ef..d9d9754 100644
--- a/ui/webui/resources/cr_components/composebox/file_thumbnail.css
+++ b/ui/webui/resources/cr_components/composebox/file_thumbnail.css
@@ -125,7 +125,7 @@
   justify-content: center;
   opacity: 0;
   position: absolute;
-  right: 0;
+  inset-inline-end: 0;
   top: 0;
   width: 44px;
 }
@@ -137,11 +137,10 @@
     var(--color-composebox-file-carousel-remove-gradient-end) 123.53%
   );
   border-radius: 0 8px 8px 0;
-  height: 44px;
-  left: -12px;
+  inset-block: 0;
+  inset-inline-end: 0;
+  inline-size: 56px;
   position: absolute;
-  top: 0;
-  width: 56px;
   pointer-events: none;
 }
 
@@ -157,6 +156,10 @@
   position: relative;
 }
 
+:host-context([dir='rtl']) .gradient-protection {
+  transform: scaleX(-1);
+}
+
 .title {
   color: var(--color-composebox-file-chip-text);
   font-family: inherit;
diff --git a/ui/webui/resources/cr_components/composebox/recent_tab_chip.css b/ui/webui/resources/cr_components/composebox/recent_tab_chip.css
index 9301957..b92f7755 100644
--- a/ui/webui/resources/cr_components/composebox/recent_tab_chip.css
+++ b/ui/webui/resources/cr_components/composebox/recent_tab_chip.css
@@ -38,7 +38,7 @@
 }
 
 .favicon {
-  border-radius: 100px;
+  border-radius: 2px;
   height: 16px;
   width: 16px;
 }